@bod.ee/ai-avatar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2029 @@
1
+ var X = Object.defineProperty;
2
+ var I = (r, t, e) => t in r ? X(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var a = (r, t, e) => I(r, typeof t != "symbol" ? t + "" : t, e);
4
+ import { useRef as v, useState as g, useEffect as C } from "react";
5
+ class F {
6
+ constructor() {
7
+ a(this, "events", /* @__PURE__ */ new Map());
8
+ }
9
+ on(t, e) {
10
+ return this.events.has(t) || this.events.set(t, []), this.events.get(t).push(e), this;
11
+ }
12
+ off(t, e) {
13
+ const i = this.events.get(t);
14
+ if (i) {
15
+ const s = i.indexOf(e);
16
+ s !== -1 && i.splice(s, 1);
17
+ }
18
+ return this;
19
+ }
20
+ emit(t, ...e) {
21
+ const i = this.events.get(t);
22
+ return i ? (i.forEach((s) => s(...e)), !0) : !1;
23
+ }
24
+ removeAllListeners(t) {
25
+ return t ? this.events.delete(t) : this.events.clear(), this;
26
+ }
27
+ }
28
+ const Y = {
29
+ normal: 1,
30
+ // Default speed
31
+ energized: 0.6,
32
+ // 40% faster (shorter durations)
33
+ tired: 1.8
34
+ // 80% slower (longer durations)
35
+ }, b = {
36
+ // Base states
37
+ neutral: {
38
+ id: "neutral",
39
+ name: "Neutral",
40
+ state: {
41
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
42
+ mouth: { shape: "closed" }
43
+ },
44
+ transitionDuration: 150,
45
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
46
+ },
47
+ blink: {
48
+ id: "blink",
49
+ name: "Blink",
50
+ state: {
51
+ eyes: { scaleY: 0.08, scaleX: 1.05, position: { x: 0, y: 0 } },
52
+ mouth: { shape: "closed" }
53
+ },
54
+ transitionDuration: 80,
55
+ easing: "ease-out"
56
+ },
57
+ // Squint variations (eyes converge slightly inward when squinting)
58
+ squintLight: {
59
+ id: "squintLight",
60
+ name: "Light Squint",
61
+ state: {
62
+ eyes: { scaleY: 0.7, scaleX: 1, position: { x: 0, y: 0.05 } },
63
+ mouth: { shape: "closed" }
64
+ },
65
+ transitionDuration: 200,
66
+ easing: "ease-in-out"
67
+ },
68
+ squintMedium: {
69
+ id: "squintMedium",
70
+ name: "Medium Squint",
71
+ state: {
72
+ eyes: { scaleY: 0.4, scaleX: 1, position: { x: 0, y: 0.08 } },
73
+ mouth: { shape: "closed" }
74
+ },
75
+ transitionDuration: 200,
76
+ easing: "ease-in-out"
77
+ },
78
+ squint: {
79
+ id: "squint",
80
+ name: "Squint",
81
+ state: {
82
+ eyes: { scaleY: 0.15, scaleX: 1.02, position: { x: 0, y: 0.1 } },
83
+ mouth: { shape: "closed" }
84
+ },
85
+ transitionDuration: 200,
86
+ easing: "ease-in-out"
87
+ },
88
+ squintDeep: {
89
+ id: "squintDeep",
90
+ name: "Deep Squint",
91
+ state: {
92
+ eyes: { scaleY: 0.08, scaleX: 1.05, position: { x: 0, y: 0.12 } },
93
+ mouth: { shape: "closed" }
94
+ },
95
+ transitionDuration: 200,
96
+ easing: "ease-in-out"
97
+ },
98
+ // Happy/Engaged states (eyes slightly wider, subtle upward drift)
99
+ happy: {
100
+ id: "happy",
101
+ name: "Happy",
102
+ state: {
103
+ eyes: { scaleY: 0.6, scaleX: 1.05, position: { x: 0, y: 0.05 } },
104
+ mouth: { shape: "smile", openness: 0.6 }
105
+ },
106
+ transitionDuration: 100,
107
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
108
+ },
109
+ happySmall: {
110
+ id: "happySmall",
111
+ name: "Happy (Small)",
112
+ state: {
113
+ eyes: { scaleY: 0.75, scaleX: 1.02, position: { x: 0, y: 0.03 } },
114
+ mouth: { shape: "smile", openness: 0.4 }
115
+ },
116
+ transitionDuration: 100,
117
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
118
+ },
119
+ happyBig: {
120
+ id: "happyBig",
121
+ name: "Happy (Big)",
122
+ state: {
123
+ eyes: { scaleY: 0.5, scaleX: 1.08, position: { x: 0, y: 0.06 } },
124
+ mouth: { shape: "smile", openness: 0.9 }
125
+ },
126
+ transitionDuration: 100,
127
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
128
+ },
129
+ // Talking variations (subtle movement during speech)
130
+ talking1: {
131
+ id: "talking1",
132
+ name: "Talking 1",
133
+ state: {
134
+ eyes: { scaleY: 1, scaleX: 1, position: { x: -0.02, y: 0 } },
135
+ mouth: { shape: "neutral", openness: 0.4 }
136
+ },
137
+ transitionDuration: 60,
138
+ easing: "ease-out"
139
+ },
140
+ talking2: {
141
+ id: "talking2",
142
+ name: "Talking 2",
143
+ state: {
144
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0.02, y: -0.02 } },
145
+ mouth: { shape: "open", openness: 0.6 }
146
+ },
147
+ transitionDuration: 60,
148
+ easing: "ease-out"
149
+ },
150
+ talking3: {
151
+ id: "talking3",
152
+ name: "Talking 3",
153
+ state: {
154
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: -0.03 } },
155
+ mouth: { shape: "open", openness: 0.8 }
156
+ },
157
+ transitionDuration: 60,
158
+ easing: "ease-out"
159
+ },
160
+ // Emotional states - SUPER SURPRISED!
161
+ surprised: {
162
+ id: "surprised",
163
+ name: "Surprised",
164
+ state: {
165
+ eyes: { scaleY: 1.5, scaleX: 1.4, position: { x: 0, y: -0.2 } },
166
+ mouth: { shape: "O", openness: 1.1 },
167
+ face: { x: 0, y: -0.4, tilt: 0 }
168
+ // BIG pull back!
169
+ },
170
+ transitionDuration: 60,
171
+ easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)"
172
+ },
173
+ focused: {
174
+ id: "focused",
175
+ name: "Focused",
176
+ state: {
177
+ eyes: { scaleY: 0.65, scaleX: 1.05, position: { x: 0, y: 0.08 } },
178
+ mouth: { shape: "closed" },
179
+ face: { x: 0, y: 0.1, tilt: -2 }
180
+ },
181
+ transitionDuration: 300,
182
+ easing: "ease-in-out"
183
+ },
184
+ tired: {
185
+ id: "tired",
186
+ name: "Tired",
187
+ state: {
188
+ eyes: { scaleY: 0.5, scaleX: 1, position: { x: 0, y: 0.15 } },
189
+ mouth: { shape: "closed" }
190
+ },
191
+ transitionDuration: 400,
192
+ easing: "ease-out"
193
+ },
194
+ sleepy: {
195
+ id: "sleepy",
196
+ name: "Sleepy",
197
+ state: {
198
+ eyes: { scaleY: 0.25, scaleX: 1, position: { x: 0, y: 0.2 } },
199
+ mouth: { shape: "closed" }
200
+ },
201
+ transitionDuration: 500,
202
+ easing: "ease-out"
203
+ },
204
+ // Cheerful with squinted eyes (happy squint)
205
+ cheerful: {
206
+ id: "cheerful",
207
+ name: "Cheerful",
208
+ state: {
209
+ eyes: { scaleY: 0.5, scaleX: 1.05, position: { x: 0, y: 0.05 } },
210
+ mouth: { shape: "smile", openness: 0.8 },
211
+ face: { x: 0, y: -0.1, tilt: 4 }
212
+ },
213
+ transitionDuration: 150,
214
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
215
+ },
216
+ // Concentrated/Straining
217
+ straining: {
218
+ id: "straining",
219
+ name: "Straining",
220
+ state: {
221
+ eyes: { scaleY: 0.12, scaleX: 1.05, position: { x: 0, y: 0.1 } },
222
+ mouth: { shape: "closed" }
223
+ },
224
+ transitionDuration: 250,
225
+ easing: "ease-in"
226
+ },
227
+ // NEW: Looking expressions (for cursor tracking override)
228
+ lookLeft: {
229
+ id: "lookLeft",
230
+ name: "Look Left",
231
+ state: {
232
+ eyes: { scaleY: 1, scaleX: 1, position: { x: -0.3, y: 0 } },
233
+ mouth: { shape: "closed" }
234
+ },
235
+ transitionDuration: 150,
236
+ easing: "ease-out"
237
+ },
238
+ lookRight: {
239
+ id: "lookRight",
240
+ name: "Look Right",
241
+ state: {
242
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0.3, y: 0 } },
243
+ mouth: { shape: "closed" }
244
+ },
245
+ transitionDuration: 150,
246
+ easing: "ease-out"
247
+ },
248
+ lookUp: {
249
+ id: "lookUp",
250
+ name: "Look Up",
251
+ state: {
252
+ eyes: { scaleY: 1.05, scaleX: 1, position: { x: 0, y: -0.25 } },
253
+ mouth: { shape: "closed" }
254
+ },
255
+ transitionDuration: 150,
256
+ easing: "ease-out"
257
+ },
258
+ lookDown: {
259
+ id: "lookDown",
260
+ name: "Look Down",
261
+ state: {
262
+ eyes: { scaleY: 0.9, scaleX: 1, position: { x: 0, y: 0.2 } },
263
+ mouth: { shape: "closed" }
264
+ },
265
+ transitionDuration: 150,
266
+ easing: "ease-out"
267
+ },
268
+ // Curious - BIG lean and tilt!
269
+ curious: {
270
+ id: "curious",
271
+ name: "Curious",
272
+ state: {
273
+ eyes: { scaleY: 1.2, scaleX: 1.1, position: { x: 0.25, y: -0.15 } },
274
+ mouth: { shape: "neutral", openness: 0.4 },
275
+ face: { x: 0.35, y: -0.2, tilt: 15 }
276
+ // BIG lean forward and tilt!
277
+ },
278
+ transitionDuration: 200,
279
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
280
+ },
281
+ // Skeptical
282
+ skeptical: {
283
+ id: "skeptical",
284
+ name: "Skeptical",
285
+ state: {
286
+ eyes: { scaleY: 0.75, scaleX: 1, position: { x: 0.15, y: 0.05 } },
287
+ mouth: { shape: "closed" },
288
+ face: { x: -0.1, y: 0.05, tilt: -5 }
289
+ // Pull back slightly, tilt
290
+ },
291
+ transitionDuration: 250,
292
+ easing: "ease-in-out"
293
+ },
294
+ // Excited - wide eyes, bouncy - SUPER EXPRESSIVE!
295
+ excited: {
296
+ id: "excited",
297
+ name: "Excited",
298
+ state: {
299
+ eyes: { scaleY: 1.4, scaleX: 1.3, position: { x: 0, y: -0.25 } },
300
+ mouth: { shape: "smile", openness: 1.2 },
301
+ face: { x: 0, y: -0.35, tilt: 0 }
302
+ // BIG bounce up!
303
+ },
304
+ transitionDuration: 80,
305
+ easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)"
306
+ },
307
+ // Shy - REALLY turning away!
308
+ shy: {
309
+ id: "shy",
310
+ name: "Shy",
311
+ state: {
312
+ eyes: { scaleY: 0.7, scaleX: 0.85, position: { x: -0.4, y: 0.25 } },
313
+ mouth: { shape: "smile", openness: 0.3 },
314
+ face: { x: -0.45, y: 0.25, tilt: -18 }
315
+ // BIG turn away and look down!
316
+ },
317
+ transitionDuration: 300,
318
+ easing: "ease-out"
319
+ },
320
+ // Wink (asymmetric - left eye closed, right eye open)
321
+ wink: {
322
+ id: "wink",
323
+ name: "Wink",
324
+ state: {
325
+ eyes: { scaleY: 0.5, leftScaleY: 0.06, rightScaleY: 0.5, position: { x: 0.1, y: 0 } },
326
+ mouth: { shape: "smile", openness: 0.5 },
327
+ face: { x: 0.1, y: -0.05, tilt: 5 }
328
+ },
329
+ transitionDuration: 100,
330
+ easing: "ease-out"
331
+ },
332
+ // NEW: Phoneme-based talking expressions
333
+ "talking-A": {
334
+ id: "talking-A",
335
+ name: "Talking (A)",
336
+ state: {
337
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
338
+ mouth: { shape: "A", openness: 0.7 }
339
+ },
340
+ transitionDuration: 60,
341
+ easing: "ease-in-out"
342
+ },
343
+ "talking-E": {
344
+ id: "talking-E",
345
+ name: "Talking (E)",
346
+ state: {
347
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
348
+ mouth: { shape: "E", openness: 0.5 }
349
+ },
350
+ transitionDuration: 60,
351
+ easing: "ease-in-out"
352
+ },
353
+ "talking-I": {
354
+ id: "talking-I",
355
+ name: "Talking (I)",
356
+ state: {
357
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
358
+ mouth: { shape: "I", openness: 0.4 }
359
+ },
360
+ transitionDuration: 60,
361
+ easing: "ease-in-out"
362
+ },
363
+ "talking-O": {
364
+ id: "talking-O",
365
+ name: "Talking (O)",
366
+ state: {
367
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
368
+ mouth: { shape: "O", openness: 0.8 }
369
+ },
370
+ transitionDuration: 60,
371
+ easing: "ease-in-out"
372
+ },
373
+ "talking-U": {
374
+ id: "talking-U",
375
+ name: "Talking (U)",
376
+ state: {
377
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
378
+ mouth: { shape: "U", openness: 0.6 }
379
+ },
380
+ transitionDuration: 60,
381
+ easing: "ease-in-out"
382
+ },
383
+ "talking-M": {
384
+ id: "talking-M",
385
+ name: "Talking (M)",
386
+ state: {
387
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
388
+ mouth: { shape: "M" }
389
+ },
390
+ transitionDuration: 60,
391
+ easing: "ease-in-out"
392
+ },
393
+ "talking-F": {
394
+ id: "talking-F",
395
+ name: "Talking (F)",
396
+ state: {
397
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
398
+ mouth: { shape: "F", openness: 0.3 }
399
+ },
400
+ transitionDuration: 60,
401
+ easing: "ease-in-out"
402
+ },
403
+ "talking-W": {
404
+ id: "talking-W",
405
+ name: "Talking (W)",
406
+ state: {
407
+ eyes: { scaleY: 1, scaleX: 1, position: { x: 0, y: 0 } },
408
+ mouth: { shape: "W", openness: 0.5 }
409
+ },
410
+ transitionDuration: 60,
411
+ easing: "ease-in-out"
412
+ },
413
+ // EXTREME EXPRESSIONS - Maximum expressiveness!
414
+ hyper: {
415
+ id: "hyper",
416
+ name: "Hyper",
417
+ state: {
418
+ eyes: { scaleY: 1.6, scaleX: 1.5, position: { x: 0.3, y: -0.3 } },
419
+ mouth: { shape: "smile", openness: 1.5 },
420
+ face: { x: 0.4, y: -0.5, tilt: 12 }
421
+ },
422
+ transitionDuration: 60,
423
+ easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)"
424
+ },
425
+ shocked: {
426
+ id: "shocked",
427
+ name: "Shocked",
428
+ state: {
429
+ eyes: { scaleY: 1.3, scaleX: 1.2, position: { x: 0, y: -0.2 } },
430
+ mouth: { shape: "O", openness: 1 },
431
+ face: { x: 0, y: -0.3, tilt: 0 }
432
+ },
433
+ transitionDuration: 40,
434
+ easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)"
435
+ },
436
+ crazy: {
437
+ id: "crazy",
438
+ name: "Crazy",
439
+ state: {
440
+ eyes: { scaleY: 1.5, scaleX: 1.4, position: { x: 0.5, y: -0.2 } },
441
+ mouth: { shape: "open", openness: 1.3 },
442
+ face: { x: 0.5, y: -0.3, tilt: 20 }
443
+ },
444
+ transitionDuration: 50,
445
+ easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)"
446
+ },
447
+ sneaky: {
448
+ id: "sneaky",
449
+ name: "Sneaky",
450
+ state: {
451
+ eyes: { scaleY: 0.4, scaleX: 0.9, position: { x: 0.4, y: 0.15 } },
452
+ mouth: { shape: "smile", openness: 0.5 },
453
+ face: { x: 0.3, y: 0.2, tilt: -12 }
454
+ },
455
+ transitionDuration: 200,
456
+ easing: "ease-in-out"
457
+ },
458
+ listening: {
459
+ id: "listening",
460
+ name: "Listening",
461
+ state: {
462
+ eyes: { scaleY: 1, leftScaleY: 0.85, rightScaleY: 1.2, position: { x: 0.08, y: -0.05 } },
463
+ mouth: { shape: "closed" },
464
+ face: { x: 0.12, y: -0.08, tilt: 6 }
465
+ },
466
+ transitionDuration: 250,
467
+ easing: "ease-in-out"
468
+ },
469
+ dramatic: {
470
+ id: "dramatic",
471
+ name: "Dramatic",
472
+ state: {
473
+ eyes: { scaleY: 1.3, scaleX: 1.2, position: { x: -0.3, y: -0.2 } },
474
+ mouth: { shape: "O", openness: 1 },
475
+ face: { x: -0.4, y: -0.3, tilt: -15 }
476
+ },
477
+ transitionDuration: 150,
478
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
479
+ },
480
+ // Personality expressions
481
+ flirty: {
482
+ id: "flirty",
483
+ name: "Flirty",
484
+ state: {
485
+ eyes: { scaleY: 0.55, scaleX: 1.05, leftScaleY: 0.08, rightScaleY: 0.55, position: { x: 0.15, y: 0.05 } },
486
+ mouth: { shape: "smile", openness: 0.5 },
487
+ face: { x: 0.15, y: -0.1, tilt: 8 }
488
+ },
489
+ transitionDuration: 200,
490
+ easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
491
+ },
492
+ sassy: {
493
+ id: "sassy",
494
+ name: "Sassy",
495
+ state: {
496
+ eyes: { scaleY: 0.45, scaleX: 1, leftScaleY: 0.7, rightScaleY: 0.25, position: { x: -0.1, y: 0.08 } },
497
+ mouth: { shape: "smile", openness: 0.4 },
498
+ face: { x: -0.2, y: 0.1, tilt: -10 }
499
+ },
500
+ transitionDuration: 180,
501
+ easing: "ease-in-out"
502
+ },
503
+ smug: {
504
+ id: "smug",
505
+ name: "Smug",
506
+ state: {
507
+ eyes: { scaleY: 0.6, scaleX: 0.95, position: { x: 0.2, y: 0.08 } },
508
+ mouth: { shape: "smile", openness: 0.35 },
509
+ face: { x: 0.15, y: 0.15, tilt: -7 }
510
+ },
511
+ transitionDuration: 250,
512
+ easing: "ease-in-out"
513
+ },
514
+ pouty: {
515
+ id: "pouty",
516
+ name: "Pouty",
517
+ state: {
518
+ eyes: { scaleY: 1.1, scaleX: 1.05, position: { x: 0, y: -0.1 } },
519
+ mouth: { shape: "U", openness: 0.5 },
520
+ face: { x: 0, y: -0.15, tilt: 3 }
521
+ },
522
+ transitionDuration: 200,
523
+ easing: "ease-out"
524
+ }
525
+ }, B = Object.values(b), k = (r) => b[r], U = () => b.neutral.state, M = {
526
+ /**
527
+ * Demo Loop - The signature 19.7 second animation
528
+ * Calm → Straining → Happy → Calm → Straining → Happy
529
+ */
530
+ demoLoop: {
531
+ id: "demoLoop",
532
+ name: "Demo Loop",
533
+ loop: !0,
534
+ frames: [
535
+ // Phase 1: Initial State (~2.5s) - Neutral/Calm
536
+ { expression: "neutral", duration: 2500, transitionDuration: 0 },
537
+ // Phase 2: Eye Squint (~3.5s) - Gradual narrowing
538
+ { expression: "squintLight", duration: 500, transitionDuration: 300 },
539
+ { expression: "squintMedium", duration: 600, transitionDuration: 300 },
540
+ { expression: "squint", duration: 800, transitionDuration: 300 },
541
+ { expression: "squintDeep", duration: 1300, transitionDuration: 400 },
542
+ // Phase 3: Open with Mouth (~3s) - Eyes open, mouth appears and grows
543
+ { expression: "happySmall", duration: 800, transitionDuration: 150 },
544
+ { expression: "happy", duration: 1e3, transitionDuration: 200 },
545
+ { expression: "happyBig", duration: 1200, transitionDuration: 200 },
546
+ // Phase 4: Mouth Disappears (~0.5s) - Instant mouth off
547
+ { expression: "neutral", duration: 500, transitionDuration: 50 },
548
+ // Phase 5: Second Squint (~5.5s) - Longer sustained squint
549
+ { expression: "squintLight", duration: 700, transitionDuration: 400 },
550
+ { expression: "squintMedium", duration: 900, transitionDuration: 400 },
551
+ { expression: "squint", duration: 1200, transitionDuration: 400 },
552
+ { expression: "squintDeep", duration: 2700, transitionDuration: 500 },
553
+ // Phase 6: Open with Mouth Again (~4.5s) - Eyes open, mouth reappears
554
+ { expression: "happySmall", duration: 1e3, transitionDuration: 200 },
555
+ { expression: "happy", duration: 1500, transitionDuration: 250 },
556
+ { expression: "happyBig", duration: 2e3, transitionDuration: 300 }
557
+ ]
558
+ },
559
+ /**
560
+ * Simple Blink
561
+ */
562
+ blink: {
563
+ id: "blink",
564
+ name: "Blink",
565
+ loop: !1,
566
+ frames: [
567
+ { expression: "blink", duration: 80, transitionDuration: 60 },
568
+ { expression: "neutral", duration: 0, transitionDuration: 80 }
569
+ ]
570
+ },
571
+ /**
572
+ * Double Blink
573
+ */
574
+ doubleBlink: {
575
+ id: "doubleBlink",
576
+ name: "Double Blink",
577
+ loop: !1,
578
+ frames: [
579
+ { expression: "blink", duration: 80, transitionDuration: 60 },
580
+ { expression: "neutral", duration: 150, transitionDuration: 80 },
581
+ { expression: "blink", duration: 80, transitionDuration: 60 },
582
+ { expression: "neutral", duration: 0, transitionDuration: 80 }
583
+ ]
584
+ },
585
+ /**
586
+ * Slow Blink (tired)
587
+ */
588
+ slowBlink: {
589
+ id: "slowBlink",
590
+ name: "Slow Blink",
591
+ loop: !1,
592
+ frames: [
593
+ { expression: "tired", duration: 200, transitionDuration: 200 },
594
+ { expression: "sleepy", duration: 300, transitionDuration: 200 },
595
+ { expression: "blink", duration: 200, transitionDuration: 150 },
596
+ { expression: "sleepy", duration: 200, transitionDuration: 200 },
597
+ { expression: "neutral", duration: 0, transitionDuration: 400 }
598
+ ]
599
+ },
600
+ /**
601
+ * Wake Up Sequence
602
+ */
603
+ wakeUp: {
604
+ id: "wakeUp",
605
+ name: "Wake Up",
606
+ loop: !1,
607
+ frames: [
608
+ { expression: "blink", duration: 500, transitionDuration: 0 },
609
+ { expression: "sleepy", duration: 400, transitionDuration: 300 },
610
+ { expression: "tired", duration: 400, transitionDuration: 300 },
611
+ { expression: "squintLight", duration: 300, transitionDuration: 200 },
612
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
613
+ ]
614
+ },
615
+ /**
616
+ * Surprised Reaction
617
+ */
618
+ surprised: {
619
+ id: "surprised",
620
+ name: "Surprised",
621
+ loop: !1,
622
+ frames: [
623
+ { expression: "surprised", duration: 800, transitionDuration: 80 },
624
+ { expression: "neutral", duration: 0, transitionDuration: 300 }
625
+ ]
626
+ },
627
+ /**
628
+ * Thinking/Processing
629
+ */
630
+ thinking: {
631
+ id: "thinking",
632
+ name: "Thinking",
633
+ loop: !1,
634
+ frames: [
635
+ { expression: "focused", duration: 600, transitionDuration: 200 },
636
+ { expression: "squintLight", duration: 400, transitionDuration: 200 },
637
+ { expression: "focused", duration: 600, transitionDuration: 200 },
638
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
639
+ ]
640
+ },
641
+ /**
642
+ * Greeting (blink then happy)
643
+ */
644
+ greeting: {
645
+ id: "greeting",
646
+ name: "Greeting",
647
+ loop: !1,
648
+ frames: [
649
+ { expression: "blink", duration: 100, transitionDuration: 80 },
650
+ { expression: "happy", duration: 1500, transitionDuration: 100 },
651
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
652
+ ]
653
+ },
654
+ /**
655
+ * Talking Animation Loop
656
+ */
657
+ talking: {
658
+ id: "talking",
659
+ name: "Talking",
660
+ loop: !0,
661
+ frames: [
662
+ { expression: "talking1", duration: 80, transitionDuration: 50 },
663
+ { expression: "talking2", duration: 100, transitionDuration: 50 },
664
+ { expression: "talking3", duration: 100, transitionDuration: 50 },
665
+ { expression: "talking2", duration: 80, transitionDuration: 50 },
666
+ { expression: "talking1", duration: 60, transitionDuration: 50 },
667
+ { expression: "happy", duration: 120, transitionDuration: 50 }
668
+ ]
669
+ },
670
+ /**
671
+ * Wink — quick asymmetric blink, smirk, back to neutral
672
+ */
673
+ wink: {
674
+ id: "wink",
675
+ name: "Wink",
676
+ loop: !1,
677
+ frames: [
678
+ { expression: "wink", duration: 120, transitionDuration: 80 },
679
+ { expression: "happySmall", duration: 400, transitionDuration: 100 },
680
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
681
+ ]
682
+ },
683
+ /**
684
+ * Flirty — double eyebrow flash (wide eyes raised twice, lean in) then settle smug
685
+ */
686
+ flirty: {
687
+ id: "flirty",
688
+ name: "Flirty",
689
+ loop: !1,
690
+ frames: [
691
+ // First flash — eyes wide, lean forward
692
+ { state: { eyes: { scaleY: 1.4, scaleX: 1.2, position: { x: 0, y: -0.2 } }, mouth: { shape: "smile", openness: 0.3 }, face: { x: 0.1, y: -0.25, tilt: 5 } }, duration: 160, transitionDuration: 80 },
693
+ // Drop back
694
+ { state: { eyes: { scaleY: 0.9, scaleX: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 120, transitionDuration: 100 },
695
+ // Second flash — wider, more lean
696
+ { state: { eyes: { scaleY: 1.5, scaleX: 1.25, position: { x: 0, y: -0.22 } }, mouth: { shape: "smile", openness: 0.4 }, face: { x: 0.15, y: -0.3, tilt: 7 } }, duration: 180, transitionDuration: 80 },
697
+ // Settle into smug
698
+ { expression: "smug", duration: 600, transitionDuration: 200 },
699
+ { expression: "neutral", duration: 0, transitionDuration: 300 }
700
+ ]
701
+ },
702
+ /**
703
+ * Idle Animation - subtle life when not doing anything
704
+ */
705
+ idle: {
706
+ id: "idle",
707
+ name: "Idle",
708
+ loop: !0,
709
+ frames: [
710
+ { expression: "neutral", duration: 3e3, transitionDuration: 0 },
711
+ { expression: "blink", duration: 80, transitionDuration: 60 },
712
+ { expression: "neutral", duration: 4e3, transitionDuration: 80 },
713
+ { expression: "focused", duration: 800, transitionDuration: 300 },
714
+ { expression: "neutral", duration: 3500, transitionDuration: 200 },
715
+ { expression: "blink", duration: 80, transitionDuration: 60 },
716
+ { expression: "neutral", duration: 2500, transitionDuration: 80 },
717
+ { expression: "blink", duration: 80, transitionDuration: 60 },
718
+ { expression: "neutral", duration: 200, transitionDuration: 80 },
719
+ { expression: "blink", duration: 80, transitionDuration: 60 },
720
+ { expression: "neutral", duration: 5e3, transitionDuration: 80 }
721
+ ]
722
+ },
723
+ /**
724
+ * Cheerful Reaction
725
+ */
726
+ cheerful: {
727
+ id: "cheerful",
728
+ name: "Cheerful",
729
+ loop: !1,
730
+ frames: [
731
+ { expression: "blink", duration: 100, transitionDuration: 80 },
732
+ { expression: "cheerful", duration: 2e3, transitionDuration: 100 },
733
+ { expression: "happy", duration: 500, transitionDuration: 150 },
734
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
735
+ ]
736
+ },
737
+ /**
738
+ * Glitch Mood Change - erratic digital transition
739
+ */
740
+ glitchMood: {
741
+ id: "glitchMood",
742
+ name: "Glitch Mood",
743
+ loop: !1,
744
+ frames: [
745
+ { expression: "neutral", duration: 50, transitionDuration: 0 },
746
+ { expression: "surprised", duration: 40, transitionDuration: 20 },
747
+ { expression: "blink", duration: 30, transitionDuration: 10 },
748
+ { expression: "squint", duration: 50, transitionDuration: 20 },
749
+ { expression: "happy", duration: 40, transitionDuration: 15 },
750
+ { expression: "neutral", duration: 30, transitionDuration: 10 },
751
+ { expression: "excited", duration: 60, transitionDuration: 20 },
752
+ { expression: "blink", duration: 40, transitionDuration: 15 },
753
+ { expression: "cheerful", duration: 800, transitionDuration: 50 },
754
+ { expression: "neutral", duration: 0, transitionDuration: 150 }
755
+ ]
756
+ },
757
+ /**
758
+ * Active Listening - subtle nods and attentive eye shifts
759
+ */
760
+ listening: {
761
+ id: "listening",
762
+ name: "Listening",
763
+ loop: !0,
764
+ frames: [
765
+ // Settle into listening pose
766
+ { expression: "listening", duration: 1800, transitionDuration: 250 },
767
+ // Subtle nod (I'm following)
768
+ { state: { eyes: { scaleY: 1, leftScaleY: 0.8, rightScaleY: 1.15, position: { x: 0.08, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: 0.12, y: 0.12, tilt: 8 } }, duration: 200, transitionDuration: 120 },
769
+ { expression: "listening", duration: 1400, transitionDuration: 150 },
770
+ // Blink
771
+ { state: { eyes: { scaleY: 0.08, scaleX: 1.05 }, mouth: { shape: "closed" }, face: { x: 0.12, y: -0.08, tilt: 6 } }, duration: 80, transitionDuration: 60 },
772
+ { expression: "listening", duration: 2e3, transitionDuration: 100 },
773
+ // Slight tilt the other way (processing)
774
+ { state: { eyes: { scaleY: 1, leftScaleY: 1.1, rightScaleY: 0.9, position: { x: -0.05, y: -0.03 } }, mouth: { shape: "closed" }, face: { x: -0.08, y: -0.05, tilt: -4 } }, duration: 1200, transitionDuration: 300 },
775
+ // Another small nod
776
+ { state: { eyes: { scaleY: 1, leftScaleY: 0.85, rightScaleY: 1.15, position: { x: 0.06, y: 0.08 } }, mouth: { shape: "closed" }, face: { x: 0.1, y: 0.1, tilt: 7 } }, duration: 180, transitionDuration: 100 },
777
+ { expression: "listening", duration: 1600, transitionDuration: 200 },
778
+ // Blink
779
+ { state: { eyes: { scaleY: 0.08, scaleX: 1.05 }, mouth: { shape: "closed" }, face: { x: 0.12, y: -0.08, tilt: 6 } }, duration: 80, transitionDuration: 60 },
780
+ { expression: "listening", duration: 1500, transitionDuration: 100 }
781
+ ]
782
+ },
783
+ /**
784
+ * Curious Scan - looking around
785
+ */
786
+ curiousScan: {
787
+ id: "curiousScan",
788
+ name: "Curious Scan",
789
+ loop: !1,
790
+ frames: [
791
+ { expression: "curious", duration: 400, transitionDuration: 150 },
792
+ { expression: "lookLeft", duration: 600, transitionDuration: 200 },
793
+ { expression: "neutral", duration: 200, transitionDuration: 100 },
794
+ { expression: "lookRight", duration: 600, transitionDuration: 200 },
795
+ { expression: "curious", duration: 300, transitionDuration: 100 },
796
+ { expression: "lookUp", duration: 400, transitionDuration: 150 },
797
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
798
+ ]
799
+ },
800
+ /**
801
+ * Shy Reaction
802
+ */
803
+ shyReaction: {
804
+ id: "shyReaction",
805
+ name: "Shy Reaction",
806
+ loop: !1,
807
+ frames: [
808
+ { expression: "surprised", duration: 150, transitionDuration: 50 },
809
+ { expression: "shy", duration: 1500, transitionDuration: 200 },
810
+ { expression: "blink", duration: 80, transitionDuration: 60 },
811
+ { expression: "happySmall", duration: 800, transitionDuration: 150 },
812
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
813
+ ]
814
+ },
815
+ /**
816
+ * Wink
817
+ */
818
+ winkSequence: {
819
+ id: "winkSequence",
820
+ name: "Wink",
821
+ loop: !1,
822
+ frames: [
823
+ { expression: "wink", duration: 300, transitionDuration: 60 },
824
+ { expression: "happySmall", duration: 400, transitionDuration: 80 },
825
+ { expression: "neutral", duration: 0, transitionDuration: 150 }
826
+ ]
827
+ },
828
+ /**
829
+ * Digital Awakening - glitchy startup
830
+ */
831
+ digitalAwaken: {
832
+ id: "digitalAwaken",
833
+ name: "Digital Awaken",
834
+ loop: !1,
835
+ frames: [
836
+ { expression: "blink", duration: 200, transitionDuration: 0 },
837
+ { expression: "sleepy", duration: 100, transitionDuration: 50 },
838
+ { expression: "blink", duration: 50, transitionDuration: 30 },
839
+ { expression: "tired", duration: 80, transitionDuration: 40 },
840
+ { expression: "blink", duration: 40, transitionDuration: 20 },
841
+ { expression: "squintLight", duration: 60, transitionDuration: 30 },
842
+ { expression: "surprised", duration: 150, transitionDuration: 40 },
843
+ { expression: "curious", duration: 400, transitionDuration: 100 },
844
+ { expression: "happy", duration: 600, transitionDuration: 80 },
845
+ { expression: "neutral", duration: 0, transitionDuration: 150 }
846
+ ]
847
+ },
848
+ /**
849
+ * Head Shake "No" - PERFECTED natural shake
850
+ * Feels like a real human shaking their head emphatically
851
+ */
852
+ headShakeNo: {
853
+ id: "headShakeNo",
854
+ name: "Head Shake No",
855
+ loop: !1,
856
+ frames: [
857
+ // Subtle anticipation - slight lean right before going left
858
+ { state: { eyes: { scaleY: 1, position: { x: 0.08, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.1, y: 0, tilt: 2 } }, duration: 60, transitionDuration: 50 },
859
+ // First shake LEFT - strong and emphatic
860
+ { state: { eyes: { scaleY: 1, position: { x: -0.55, y: 0 } }, mouth: { shape: "closed" }, face: { x: -0.65, y: 0.03, tilt: -16 } }, duration: 110, transitionDuration: 85 },
861
+ // Swing through center to RIGHT - peak momentum
862
+ { state: { eyes: { scaleY: 1, position: { x: 0.6, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.7, y: 0.03, tilt: 18 } }, duration: 120, transitionDuration: 100 },
863
+ // Back LEFT - still strong
864
+ { state: { eyes: { scaleY: 1, position: { x: -0.5, y: 0 } }, mouth: { shape: "closed" }, face: { x: -0.6, y: 0.02, tilt: -15 } }, duration: 110, transitionDuration: 95 },
865
+ // To RIGHT - momentum decreasing
866
+ { state: { eyes: { scaleY: 1, position: { x: 0.45, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.55, y: 0.02, tilt: 14 } }, duration: 105, transitionDuration: 90 },
867
+ // Final LEFT - much softer
868
+ { state: { eyes: { scaleY: 1, position: { x: -0.3, y: 0 } }, mouth: { shape: "closed" }, face: { x: -0.35, y: 0, tilt: -9 } }, duration: 95, transitionDuration: 85 },
869
+ // Small right correction
870
+ { state: { eyes: { scaleY: 1, position: { x: 0.12, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.15, y: 0, tilt: 4 } }, duration: 85, transitionDuration: 80 },
871
+ // Final settle with micro overshoot
872
+ { state: { eyes: { scaleY: 1, position: { x: -0.03, y: 0 } }, mouth: { shape: "closed" }, face: { x: -0.04, y: 0, tilt: -1 } }, duration: 70, transitionDuration: 90 },
873
+ // Rest at center
874
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 100 }
875
+ ]
876
+ },
877
+ /**
878
+ * Head Nod "Yes" - PERFECTED weighted nod
879
+ * Natural gravity-driven head bobbing with realistic bounce
880
+ */
881
+ headNodYes: {
882
+ id: "headNodYes",
883
+ name: "Head Nod Yes",
884
+ loop: !1,
885
+ frames: [
886
+ // Anticipation - head lifts slightly before first drop
887
+ { state: { eyes: { scaleY: 1.08, position: { x: 0, y: -0.12 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.18, tilt: -5 } }, duration: 90, transitionDuration: 75 },
888
+ // First NOD DOWN - gravity pulls, eyes squish
889
+ { state: { eyes: { scaleY: 0.8, scaleX: 1.08, position: { x: 0, y: 0.35 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.55, tilt: 14 } }, duration: 130, transitionDuration: 95 },
890
+ // Bounce UP - elastic rebound
891
+ { state: { eyes: { scaleY: 1.05, position: { x: 0, y: -0.08 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.12, tilt: -3 } }, duration: 90, transitionDuration: 75 },
892
+ // Second NOD DOWN - still strong
893
+ { state: { eyes: { scaleY: 0.82, scaleX: 1.06, position: { x: 0, y: 0.32 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.5, tilt: 13 } }, duration: 120, transitionDuration: 90 },
894
+ // Bounce UP - smaller rebound
895
+ { state: { eyes: { scaleY: 1.03, position: { x: 0, y: -0.05 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.08, tilt: -2 } }, duration: 80, transitionDuration: 70 },
896
+ // Third NOD DOWN - gentler
897
+ { state: { eyes: { scaleY: 0.88, scaleX: 1.04, position: { x: 0, y: 0.22 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.35, tilt: 9 } }, duration: 110, transitionDuration: 85 },
898
+ // Small bounce up
899
+ { state: { eyes: { scaleY: 1.01, position: { x: 0, y: -0.02 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.03, tilt: -1 } }, duration: 75, transitionDuration: 65 },
900
+ // Final settle - tiny overshoot
901
+ { state: { eyes: { scaleY: 0.98, position: { x: 0, y: 0.03 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.04, tilt: 1 } }, duration: 70, transitionDuration: 80 },
902
+ // Rest at neutral
903
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 100 }
904
+ ]
905
+ },
906
+ /**
907
+ * Quick Nod - fast single affirmative nod
908
+ */
909
+ quickNod: {
910
+ id: "quickNod",
911
+ name: "Quick Nod",
912
+ loop: !1,
913
+ frames: [
914
+ // Quick drop
915
+ { state: { eyes: { scaleY: 0.85, scaleX: 1.06, position: { x: 0, y: 0.3 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.45, tilt: 12 } }, duration: 100, transitionDuration: 70 },
916
+ // Quick return
917
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 100 }
918
+ ]
919
+ },
920
+ /**
921
+ * Strong No - emphatic single shake
922
+ */
923
+ strongNo: {
924
+ id: "strongNo",
925
+ name: "Strong No",
926
+ loop: !1,
927
+ frames: [
928
+ // Wind up right
929
+ { state: { eyes: { scaleY: 1, position: { x: 0.1, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.12, y: 0, tilt: 3 } }, duration: 60, transitionDuration: 50 },
930
+ // STRONG shake left
931
+ { state: { eyes: { scaleY: 1, position: { x: -0.7, y: 0 } }, mouth: { shape: "closed" }, face: { x: -0.8, y: 0.05, tilt: -20 } }, duration: 130, transitionDuration: 90 },
932
+ // Overshoot right
933
+ { state: { eyes: { scaleY: 1, position: { x: 0.5, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.6, y: 0.03, tilt: 15 } }, duration: 120, transitionDuration: 100 },
934
+ // Settle left slightly
935
+ { state: { eyes: { scaleY: 1, position: { x: -0.15, y: 0 } }, mouth: { shape: "closed" }, face: { x: -0.18, y: 0, tilt: -5 } }, duration: 90, transitionDuration: 95 },
936
+ // Return to center
937
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 110 }
938
+ ]
939
+ },
940
+ /**
941
+ * Head Tilt Confused - back and forth contemplation
942
+ */
943
+ headTiltConfused: {
944
+ id: "headTiltConfused",
945
+ name: "Head Tilt Confused",
946
+ loop: !1,
947
+ frames: [
948
+ { state: { eyes: { scaleY: 1.1, position: { x: 0.2, y: -0.1 } }, mouth: { shape: "neutral", openness: 0.3 }, face: { x: 0.15, y: -0.1, tilt: 18 } }, duration: 300, transitionDuration: 150 },
949
+ { state: { eyes: { scaleY: 1.05, position: { x: 0, y: 0 } }, mouth: { shape: "neutral", openness: 0.3 }, face: { x: 0, y: 0, tilt: 0 } }, duration: 200, transitionDuration: 120 },
950
+ { state: { eyes: { scaleY: 1.1, position: { x: -0.2, y: -0.1 } }, mouth: { shape: "neutral", openness: 0.3 }, face: { x: -0.15, y: -0.1, tilt: -18 } }, duration: 300, transitionDuration: 150 },
951
+ { state: { eyes: { scaleY: 1.05, position: { x: 0, y: 0 } }, mouth: { shape: "neutral", openness: 0.3 }, face: { x: 0, y: 0, tilt: 0 } }, duration: 200, transitionDuration: 120 },
952
+ { state: { eyes: { scaleY: 1.1, position: { x: 0.15, y: -0.1 } }, mouth: { shape: "neutral", openness: 0.4 }, face: { x: 0.1, y: -0.05, tilt: 12 } }, duration: 250, transitionDuration: 130 },
953
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 180 }
954
+ ]
955
+ },
956
+ /**
957
+ * Look Around Scan - slow deliberate 360 scan
958
+ */
959
+ lookAroundScan: {
960
+ id: "lookAroundScan",
961
+ name: "Look Around Scan",
962
+ loop: !1,
963
+ frames: [
964
+ // Start center
965
+ { state: { eyes: { scaleY: 1.05, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 150, transitionDuration: 0 },
966
+ // Look right with lean
967
+ { state: { eyes: { scaleY: 1.05, position: { x: 0.5, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.6, y: 0.05, tilt: 10 } }, duration: 400, transitionDuration: 250 },
968
+ // Continue right, look down
969
+ { state: { eyes: { scaleY: 0.9, position: { x: 0.4, y: 0.3 } }, mouth: { shape: "closed" }, face: { x: 0.5, y: 0.35, tilt: 8 } }, duration: 350, transitionDuration: 220 },
970
+ // Center bottom
971
+ { state: { eyes: { scaleY: 0.85, position: { x: 0, y: 0.4 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.45, tilt: 5 } }, duration: 350, transitionDuration: 200 },
972
+ // Left and down
973
+ { state: { eyes: { scaleY: 0.9, position: { x: -0.4, y: 0.3 } }, mouth: { shape: "closed" }, face: { x: -0.5, y: 0.35, tilt: -8 } }, duration: 350, transitionDuration: 220 },
974
+ // Far left
975
+ { state: { eyes: { scaleY: 1.05, position: { x: -0.5, y: 0 } }, mouth: { shape: "closed" }, face: { x: -0.6, y: 0.05, tilt: -10 } }, duration: 400, transitionDuration: 250 },
976
+ // Left and up
977
+ { state: { eyes: { scaleY: 1.15, position: { x: -0.3, y: -0.3 } }, mouth: { shape: "closed" }, face: { x: -0.35, y: -0.35, tilt: -6 } }, duration: 350, transitionDuration: 220 },
978
+ // Center top
979
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: -0.4 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.45, tilt: 0 } }, duration: 350, transitionDuration: 200 },
980
+ // Right and up
981
+ { state: { eyes: { scaleY: 1.15, position: { x: 0.3, y: -0.3 } }, mouth: { shape: "closed" }, face: { x: 0.35, y: -0.35, tilt: 6 } }, duration: 350, transitionDuration: 220 },
982
+ // Back to center
983
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 300 }
984
+ ]
985
+ },
986
+ /**
987
+ * Excited Bounce - bouncy happy movement
988
+ */
989
+ excitedBounce: {
990
+ id: "excitedBounce",
991
+ name: "Excited Bounce",
992
+ loop: !1,
993
+ frames: [
994
+ // Big bounce up
995
+ { state: { eyes: { scaleY: 1.4, position: { x: 0, y: -0.3 } }, mouth: { shape: "smile", openness: 1.2 }, face: { x: 0, y: -0.7, tilt: 0 } }, duration: 100, transitionDuration: 60 },
996
+ // Drop with overshoot
997
+ { state: { eyes: { scaleY: 0.9, position: { x: 0, y: 0.15 } }, mouth: { shape: "smile", openness: 1 }, face: { x: 0, y: 0.25, tilt: 0 } }, duration: 90, transitionDuration: 70 },
998
+ // Second bounce - less height
999
+ { state: { eyes: { scaleY: 1.3, position: { x: 0, y: -0.25 } }, mouth: { shape: "smile", openness: 1.1 }, face: { x: 0, y: -0.55, tilt: 0 } }, duration: 85, transitionDuration: 55 },
1000
+ { state: { eyes: { scaleY: 0.95, position: { x: 0, y: 0.1 } }, mouth: { shape: "smile", openness: 0.9 }, face: { x: 0, y: 0.18, tilt: 0 } }, duration: 80, transitionDuration: 60 },
1001
+ // Third bounce - smallest
1002
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: -0.15 } }, mouth: { shape: "smile", openness: 1 }, face: { x: 0, y: -0.35, tilt: 0 } }, duration: 75, transitionDuration: 50 },
1003
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0.05 } }, mouth: { shape: "smile", openness: 0.85 }, face: { x: 0, y: 0.1, tilt: 0 } }, duration: 70, transitionDuration: 55 },
1004
+ // Settle
1005
+ { state: { eyes: { scaleY: 1.1, position: { x: 0, y: -0.05 } }, mouth: { shape: "smile", openness: 0.9 }, face: { x: 0, y: -0.1, tilt: 0 } }, duration: 150, transitionDuration: 100 },
1006
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 150 }
1007
+ ]
1008
+ },
1009
+ /**
1010
+ * Lean In - curious lean forward
1011
+ */
1012
+ leanInCurious: {
1013
+ id: "leanInCurious",
1014
+ name: "Lean In Curious",
1015
+ loop: !1,
1016
+ frames: [
1017
+ // Slow lean in with growing curiosity
1018
+ { state: { eyes: { scaleY: 1.1, position: { x: 0, y: -0.1 } }, mouth: { shape: "neutral", openness: 0.3 }, face: { x: 0.2, y: -0.15, tilt: 5 } }, duration: 250, transitionDuration: 180 },
1019
+ { state: { eyes: { scaleY: 1.15, position: { x: 0.1, y: -0.15 } }, mouth: { shape: "neutral", openness: 0.4 }, face: { x: 0.4, y: -0.3, tilt: 10 } }, duration: 250, transitionDuration: 180 },
1020
+ { state: { eyes: { scaleY: 1.2, position: { x: 0.15, y: -0.2 } }, mouth: { shape: "neutral", openness: 0.5 }, face: { x: 0.6, y: -0.45, tilt: 15 } }, duration: 300, transitionDuration: 200 },
1021
+ // Hold the lean
1022
+ { state: { eyes: { scaleY: 1.25, position: { x: 0.2, y: -0.25 } }, mouth: { shape: "O", openness: 0.6 }, face: { x: 0.7, y: -0.5, tilt: 18 } }, duration: 400, transitionDuration: 100 },
1023
+ // Pull back quickly
1024
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 250 }
1025
+ ]
1026
+ },
1027
+ /**
1028
+ * Recoil Back - startled pullback
1029
+ */
1030
+ recoilBack: {
1031
+ id: "recoilBack",
1032
+ name: "Recoil Back",
1033
+ loop: !1,
1034
+ frames: [
1035
+ // Instant recoil
1036
+ { state: { eyes: { scaleY: 1.6, scaleX: 1.5, position: { x: 0, y: -0.3 } }, mouth: { shape: "O", openness: 1.3 }, face: { x: 0, y: -0.8, tilt: 0 } }, duration: 80, transitionDuration: 30 },
1037
+ // Overshoot back
1038
+ { state: { eyes: { scaleY: 1.5, scaleX: 1.4, position: { x: 0, y: -0.25 } }, mouth: { shape: "O", openness: 1.2 }, face: { x: 0, y: -0.7, tilt: 0 } }, duration: 60, transitionDuration: 40 },
1039
+ // Hold surprised
1040
+ { state: { eyes: { scaleY: 1.55, scaleX: 1.45, position: { x: 0, y: -0.3 } }, mouth: { shape: "O", openness: 1.25 }, face: { x: 0, y: -0.75, tilt: 0 } }, duration: 300, transitionDuration: 50 },
1041
+ // Slowly return
1042
+ { state: { eyes: { scaleY: 1.3, position: { x: 0, y: -0.15 } }, mouth: { shape: "neutral", openness: 0.8 }, face: { x: 0, y: -0.4, tilt: 0 } }, duration: 200, transitionDuration: 150 },
1043
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 200 }
1044
+ ]
1045
+ },
1046
+ /**
1047
+ * Shifty Eyes - SLOW suspicious squinted side-to-side movement
1048
+ */
1049
+ shiftyEyes: {
1050
+ id: "shiftyEyes",
1051
+ name: "Shifty Eyes",
1052
+ loop: !1,
1053
+ frames: [
1054
+ // Squint and slowly look left
1055
+ { state: { eyes: { scaleY: 0.4, position: { x: -0.6, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: -0.15, y: 0.08, tilt: -4 } }, duration: 500, transitionDuration: 200 },
1056
+ // Slow deliberate dart to right
1057
+ { state: { eyes: { scaleY: 0.4, position: { x: 0.6, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: 0.15, y: 0.08, tilt: 4 } }, duration: 450, transitionDuration: 250 },
1058
+ // Slowly back to left
1059
+ { state: { eyes: { scaleY: 0.4, position: { x: -0.6, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: -0.15, y: 0.08, tilt: -4 } }, duration: 450, transitionDuration: 250 },
1060
+ // To right again - methodical
1061
+ { state: { eyes: { scaleY: 0.4, position: { x: 0.6, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: 0.15, y: 0.08, tilt: 4 } }, duration: 450, transitionDuration: 250 },
1062
+ // Slow center with suspicion - hold the squint
1063
+ { state: { eyes: { scaleY: 0.5, position: { x: 0, y: 0.05 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.05, tilt: 0 } }, duration: 500, transitionDuration: 200 },
1064
+ // One more slow left dart
1065
+ { state: { eyes: { scaleY: 0.4, position: { x: -0.5, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: -0.12, y: 0.08, tilt: -3 } }, duration: 400, transitionDuration: 180 },
1066
+ // Slowly return to neutral
1067
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 300 }
1068
+ ]
1069
+ },
1070
+ /**
1071
+ * Suspicious Squint - just squinting with subtle movements
1072
+ */
1073
+ suspiciousSquint: {
1074
+ id: "suspiciousSquint",
1075
+ name: "Suspicious Squint",
1076
+ loop: !1,
1077
+ frames: [
1078
+ // Start squinting
1079
+ { state: { eyes: { scaleY: 0.35, position: { x: 0, y: 0.08 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.05, tilt: 0 } }, duration: 600, transitionDuration: 200 },
1080
+ // Squint harder with slight tilt
1081
+ { state: { eyes: { scaleY: 0.25, position: { x: 0.1, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: 0.05, y: 0.08, tilt: 2 } }, duration: 800, transitionDuration: 150 },
1082
+ // Hold the intense squint
1083
+ { state: { eyes: { scaleY: 0.25, position: { x: -0.1, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: -0.05, y: 0.08, tilt: -2 } }, duration: 800, transitionDuration: 150 },
1084
+ // Gradually release
1085
+ { state: { eyes: { scaleY: 0.4, position: { x: 0, y: 0.05 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.03, tilt: 0 } }, duration: 500, transitionDuration: 200 },
1086
+ // Return to neutral
1087
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 300 }
1088
+ ]
1089
+ },
1090
+ /**
1091
+ * Eye Roll - classic dramatic eye roll
1092
+ */
1093
+ eyeRoll: {
1094
+ id: "eyeRoll",
1095
+ name: "Eye Roll",
1096
+ loop: !1,
1097
+ frames: [
1098
+ // Start center, slight squint
1099
+ { state: { eyes: { scaleY: 0.85, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 100, transitionDuration: 80 },
1100
+ // Look down first
1101
+ { state: { eyes: { scaleY: 0.75, position: { x: 0, y: 0.3 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.1, tilt: 0 } }, duration: 120, transitionDuration: 100 },
1102
+ // Roll to right-down
1103
+ { state: { eyes: { scaleY: 0.8, position: { x: 0.4, y: 0.25 } }, mouth: { shape: "closed" }, face: { x: 0.08, y: 0.08, tilt: 2 } }, duration: 100, transitionDuration: 90 },
1104
+ // Roll up-right
1105
+ { state: { eyes: { scaleY: 0.9, position: { x: 0.3, y: -0.3 } }, mouth: { shape: "closed" }, face: { x: 0.05, y: -0.05, tilt: 1 } }, duration: 110, transitionDuration: 95 },
1106
+ // Roll to up-center (peak of roll)
1107
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: -0.5 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.1, tilt: 0 } }, duration: 120, transitionDuration: 100 },
1108
+ // Roll up-left
1109
+ { state: { eyes: { scaleY: 0.9, position: { x: -0.3, y: -0.3 } }, mouth: { shape: "closed" }, face: { x: -0.05, y: -0.05, tilt: -1 } }, duration: 110, transitionDuration: 95 },
1110
+ // Back to center with attitude
1111
+ { state: { eyes: { scaleY: 0.7, position: { x: 0, y: 0.05 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.03, tilt: 0 } }, duration: 180, transitionDuration: 120 },
1112
+ // Return to neutral
1113
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 150 }
1114
+ ]
1115
+ },
1116
+ /**
1117
+ * Side Eye - judgmental sideways glance
1118
+ */
1119
+ sideEye: {
1120
+ id: "sideEye",
1121
+ name: "Side Eye",
1122
+ loop: !1,
1123
+ frames: [
1124
+ // Neutral start
1125
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 120, transitionDuration: 0 },
1126
+ // Squint and look hard to the side
1127
+ { state: { eyes: { scaleY: 0.5, position: { x: 0.7, y: 0.08 } }, mouth: { shape: "closed" }, face: { x: 0.2, y: 0.05, tilt: 5 } }, duration: 300, transitionDuration: 120 },
1128
+ // Even more squinted, judging hard
1129
+ { state: { eyes: { scaleY: 0.35, position: { x: 0.75, y: 0.1 } }, mouth: { shape: "closed" }, face: { x: 0.25, y: 0.08, tilt: 6 } }, duration: 400, transitionDuration: 100 },
1130
+ // Quick glance forward (caught!)
1131
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: -0.05 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.03, tilt: 0 } }, duration: 100, transitionDuration: 80 },
1132
+ // Return to neutral
1133
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 120 }
1134
+ ]
1135
+ },
1136
+ /**
1137
+ * Nervous Glance - anxious rapid eye movements
1138
+ */
1139
+ nervousGlance: {
1140
+ id: "nervousGlance",
1141
+ name: "Nervous Glance",
1142
+ loop: !1,
1143
+ frames: [
1144
+ // Quick dart left
1145
+ { state: { eyes: { scaleY: 1.1, position: { x: -0.5, y: -0.05 } }, mouth: { shape: "closed" }, face: { x: -0.1, y: 0, tilt: -3 } }, duration: 90, transitionDuration: 60 },
1146
+ // Quick to right
1147
+ { state: { eyes: { scaleY: 1.1, position: { x: 0.5, y: -0.05 } }, mouth: { shape: "closed" }, face: { x: 0.1, y: 0, tilt: 3 } }, duration: 85, transitionDuration: 55 },
1148
+ // Down nervously
1149
+ { state: { eyes: { scaleY: 0.85, position: { x: 0.2, y: 0.3 } }, mouth: { shape: "closed" }, face: { x: 0.05, y: 0.1, tilt: 1 } }, duration: 110, transitionDuration: 70 },
1150
+ // Up and left
1151
+ { state: { eyes: { scaleY: 1.15, position: { x: -0.4, y: -0.25 } }, mouth: { shape: "closed" }, face: { x: -0.08, y: -0.08, tilt: -2 } }, duration: 95, transitionDuration: 65 },
1152
+ // Center briefly
1153
+ { state: { eyes: { scaleY: 1.05, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 70, transitionDuration: 60 },
1154
+ // Quick right again
1155
+ { state: { eyes: { scaleY: 1.1, position: { x: 0.45, y: -0.1 } }, mouth: { shape: "closed" }, face: { x: 0.09, y: -0.02, tilt: 2 } }, duration: 80, transitionDuration: 55 },
1156
+ // Blink nervously
1157
+ { state: { eyes: { scaleY: 0.1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 60, transitionDuration: 40 },
1158
+ // Return
1159
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 100 }
1160
+ ]
1161
+ },
1162
+ /**
1163
+ * Wide Eyed Wonder - amazed looking around
1164
+ */
1165
+ wideEyedWonder: {
1166
+ id: "wideEyedWonder",
1167
+ name: "Wide Eyed Wonder",
1168
+ loop: !1,
1169
+ frames: [
1170
+ // Start with surprise
1171
+ { state: { eyes: { scaleY: 1.4, scaleX: 1.3, position: { x: 0, y: -0.2 } }, mouth: { shape: "O", openness: 0.7 }, face: { x: 0, y: -0.15, tilt: 0 } }, duration: 150, transitionDuration: 80 },
1172
+ // Look up-right in wonder
1173
+ { state: { eyes: { scaleY: 1.5, scaleX: 1.35, position: { x: 0.3, y: -0.4 } }, mouth: { shape: "O", openness: 0.8 }, face: { x: 0.15, y: -0.3, tilt: 5 } }, duration: 250, transitionDuration: 150 },
1174
+ // Pan to up-left
1175
+ { state: { eyes: { scaleY: 1.5, scaleX: 1.35, position: { x: -0.3, y: -0.4 } }, mouth: { shape: "O", openness: 0.8 }, face: { x: -0.15, y: -0.3, tilt: -5 } }, duration: 250, transitionDuration: 180 },
1176
+ // Look down in awe
1177
+ { state: { eyes: { scaleY: 1.3, scaleX: 1.25, position: { x: 0, y: 0.2 } }, mouth: { shape: "smile", openness: 0.9 }, face: { x: 0, y: 0.15, tilt: 0 } }, duration: 220, transitionDuration: 140 },
1178
+ // Back to center amazed
1179
+ { state: { eyes: { scaleY: 1.4, scaleX: 1.3, position: { x: 0, y: -0.15 } }, mouth: { shape: "smile", openness: 1 }, face: { x: 0, y: -0.12, tilt: 0 } }, duration: 200, transitionDuration: 120 },
1180
+ // Return excited
1181
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: -0.05 } }, mouth: { shape: "smile", openness: 0.8 }, face: { x: 0, y: -0.05, tilt: 0 } }, duration: 180, transitionDuration: 140 },
1182
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 150 }
1183
+ ]
1184
+ },
1185
+ /**
1186
+ * Double Take - classic surprised double take
1187
+ */
1188
+ doubleTake: {
1189
+ id: "doubleTake",
1190
+ name: "Double Take",
1191
+ loop: !1,
1192
+ frames: [
1193
+ // Looking away casually
1194
+ { state: { eyes: { scaleY: 1, position: { x: 0.4, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0.3, y: 0, tilt: 5 } }, duration: 200, transitionDuration: 120 },
1195
+ // Snap to center (first take)
1196
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: -0.05 } }, mouth: { shape: "neutral", openness: 0.4 }, face: { x: 0, y: -0.05, tilt: 0 } }, duration: 100, transitionDuration: 70 },
1197
+ // Look away again (didn't register)
1198
+ { state: { eyes: { scaleY: 0.95, position: { x: 0.35, y: 0.05 } }, mouth: { shape: "closed" }, face: { x: 0.25, y: 0.03, tilt: 4 } }, duration: 150, transitionDuration: 100 },
1199
+ // WAIT WHAT - snap back HARD (second take)
1200
+ { state: { eyes: { scaleY: 1.6, scaleX: 1.5, position: { x: 0, y: -0.3 } }, mouth: { shape: "O", openness: 1.2 }, face: { x: 0, y: -0.25, tilt: 0 } }, duration: 120, transitionDuration: 60 },
1201
+ // Hold the shock
1202
+ { state: { eyes: { scaleY: 1.55, scaleX: 1.45, position: { x: 0, y: -0.28 } }, mouth: { shape: "O", openness: 1.1 }, face: { x: 0, y: -0.23, tilt: 0 } }, duration: 350, transitionDuration: 50 },
1203
+ // Slowly process
1204
+ { state: { eyes: { scaleY: 1.3, position: { x: 0, y: -0.15 } }, mouth: { shape: "neutral", openness: 0.7 }, face: { x: 0, y: -0.12, tilt: 0 } }, duration: 200, transitionDuration: 150 },
1205
+ { state: { eyes: { scaleY: 1, position: { x: 0, y: 0 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0, tilt: 0 } }, duration: 0, transitionDuration: 180 }
1206
+ ]
1207
+ },
1208
+ /**
1209
+ * Quirky Glitch Mode - cute chaotic jumping around
1210
+ */
1211
+ quirkyGlitch: {
1212
+ id: "quirkyGlitch",
1213
+ name: "Quirky Glitch",
1214
+ loop: !1,
1215
+ frames: [
1216
+ { state: { eyes: { scaleY: 1.3, position: { x: 0.4, y: -0.3 } }, mouth: { shape: "open", openness: 0.8 }, face: { x: 0.5, y: -0.4, tilt: 15 } }, duration: 80, transitionDuration: 0 },
1217
+ { state: { eyes: { scaleY: 0.3, position: { x: -0.5, y: 0.4 } }, mouth: { shape: "smile", openness: 1 }, face: { x: -0.6, y: 0.3, tilt: -20 } }, duration: 70, transitionDuration: 0 },
1218
+ { state: { eyes: { scaleY: 1.5, position: { x: 0.2, y: -0.5 } }, mouth: { shape: "O", openness: 0.9 }, face: { x: 0.3, y: -0.5, tilt: 25 } }, duration: 60, transitionDuration: 0 },
1219
+ { state: { eyes: { scaleY: 0.5, position: { x: -0.4, y: 0.2 } }, mouth: { shape: "neutral", openness: 0.3 }, face: { x: -0.4, y: 0.4, tilt: -15 } }, duration: 90, transitionDuration: 0 },
1220
+ { state: { eyes: { scaleY: 1.4, position: { x: 0.5, y: 0.1 } }, mouth: { shape: "smile", openness: 1 }, face: { x: 0.6, y: 0.2, tilt: 18 } }, duration: 75, transitionDuration: 0 },
1221
+ { state: { eyes: { scaleY: 0.2, position: { x: -0.3, y: -0.4 } }, mouth: { shape: "closed" }, face: { x: -0.5, y: -0.3, tilt: -22 } }, duration: 65, transitionDuration: 0 },
1222
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: 0.3 } }, mouth: { shape: "open", openness: 0.7 }, face: { x: 0.2, y: 0.5, tilt: 12 } }, duration: 85, transitionDuration: 0 },
1223
+ { expression: "excited", duration: 300, transitionDuration: 100 },
1224
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
1225
+ ]
1226
+ },
1227
+ /**
1228
+ * Crazy Eyes - eyes dart around wildly
1229
+ */
1230
+ crazyEyes: {
1231
+ id: "crazyEyes",
1232
+ name: "Crazy Eyes",
1233
+ loop: !1,
1234
+ frames: [
1235
+ { state: { eyes: { scaleY: 1.2, position: { x: -0.5, y: -0.3 } }, mouth: { shape: "closed" }, face: { x: -0.2, y: 0, tilt: 0 } }, duration: 100, transitionDuration: 40 },
1236
+ { state: { eyes: { scaleY: 1.2, position: { x: 0.5, y: 0.3 } }, mouth: { shape: "closed" }, face: { x: 0.2, y: 0, tilt: 0 } }, duration: 100, transitionDuration: 40 },
1237
+ { state: { eyes: { scaleY: 1.2, position: { x: -0.4, y: 0.4 } }, mouth: { shape: "closed" }, face: { x: -0.1, y: 0.1, tilt: 0 } }, duration: 100, transitionDuration: 40 },
1238
+ { state: { eyes: { scaleY: 1.2, position: { x: 0.4, y: -0.4 } }, mouth: { shape: "closed" }, face: { x: 0.1, y: -0.1, tilt: 0 } }, duration: 100, transitionDuration: 40 },
1239
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: 0.5 } }, mouth: { shape: "closed" }, face: { x: 0, y: 0.2, tilt: 0 } }, duration: 100, transitionDuration: 40 },
1240
+ { state: { eyes: { scaleY: 1.2, position: { x: 0, y: -0.5 } }, mouth: { shape: "closed" }, face: { x: 0, y: -0.2, tilt: 0 } }, duration: 100, transitionDuration: 40 },
1241
+ { expression: "surprised", duration: 400, transitionDuration: 80 },
1242
+ { expression: "neutral", duration: 0, transitionDuration: 200 }
1243
+ ]
1244
+ }
1245
+ }, W = Object.values(M), _ = (r) => M[r], T = {
1246
+ // Expressions
1247
+ happy: { type: "expression", id: "happy" },
1248
+ cheerful: { type: "expression", id: "cheerful" },
1249
+ focused: { type: "expression", id: "focused" },
1250
+ dramatic: { type: "expression", id: "dramatic" },
1251
+ tired: { type: "expression", id: "tired" },
1252
+ sleepy: { type: "expression", id: "sleepy" },
1253
+ sneaky: { type: "expression", id: "sneaky" },
1254
+ hyper: { type: "expression", id: "hyper" },
1255
+ crazy: { type: "expression", id: "crazy" },
1256
+ wink: { type: "expression", id: "wink" },
1257
+ surprised: { type: "expression", id: "surprised" },
1258
+ curious: { type: "expression", id: "curious" },
1259
+ skeptical: { type: "expression", id: "skeptical" },
1260
+ shy: { type: "expression", id: "shy" },
1261
+ excited: { type: "expression", id: "excited" },
1262
+ shocked: { type: "expression", id: "shocked" },
1263
+ straining: { type: "expression", id: "straining" },
1264
+ flirty: { type: "expression", id: "flirty" },
1265
+ listening: { type: "expression", id: "listening" },
1266
+ // Sequences
1267
+ bouncing: { type: "sequence", id: "excitedBounce" },
1268
+ recoiling: { type: "sequence", id: "recoilBack" },
1269
+ leaning: { type: "sequence", id: "leanInCurious" },
1270
+ squinting: { type: "sequence", id: "suspiciousSquint" },
1271
+ shying: { type: "sequence", id: "shyReaction" },
1272
+ agreeing: { type: "sequence", id: "headNodYes" },
1273
+ disagreeing: { type: "sequence", id: "headShakeNo" },
1274
+ thinking: { type: "sequence", id: "thinking" },
1275
+ confused: { type: "sequence", id: "headTiltConfused" },
1276
+ playful: { type: "sequence", id: "winkSequence" },
1277
+ wonder: { type: "sequence", id: "wideEyedWonder" },
1278
+ nervous: { type: "sequence", id: "nervousGlance" },
1279
+ sassy: { type: "sequence", id: "sideEye" },
1280
+ suspicious: { type: "sequence", id: "shiftyEyes" },
1281
+ mischievous: { type: "sequence", id: "eyeRoll" },
1282
+ greeting: { type: "sequence", id: "greeting" },
1283
+ scanning: { type: "sequence", id: "lookAroundScan" },
1284
+ doubletake: { type: "sequence", id: "doubleTake" },
1285
+ glitching: { type: "sequence", id: "quirkyGlitch" },
1286
+ awakening: { type: "sequence", id: "digitalAwaken" },
1287
+ cheerfulSeq: { type: "sequence", id: "cheerful" },
1288
+ curiousScan: { type: "sequence", id: "curiousScan" },
1289
+ crazyEyes: { type: "sequence", id: "crazyEyes" },
1290
+ blink: { type: "sequence", id: "blink" },
1291
+ doubleBlink: { type: "sequence", id: "doubleBlink" },
1292
+ slowBlink: { type: "sequence", id: "slowBlink" },
1293
+ wakeUp: { type: "sequence", id: "wakeUp" },
1294
+ talking: { type: "sequence", id: "talking" },
1295
+ idle: { type: "sequence", id: "idle" },
1296
+ glitchMood: { type: "sequence", id: "glitchMood" },
1297
+ quickNod: { type: "sequence", id: "quickNod" },
1298
+ strongNo: { type: "sequence", id: "strongNo" }
1299
+ }, L = {
1300
+ sad: "shy",
1301
+ neutral: "focused",
1302
+ warm: "happy",
1303
+ friendly: "cheerful",
1304
+ serious: "focused",
1305
+ amused: "playful",
1306
+ concerned: "curious",
1307
+ empathetic: "shy",
1308
+ confident: "agreeing",
1309
+ enthusiastic: "excited",
1310
+ thoughtful: "thinking",
1311
+ impressed: "wonder",
1312
+ calm: "focused",
1313
+ angry: "disagreeing",
1314
+ frustrated: "skeptical",
1315
+ supportive: "agreeing",
1316
+ gentle: "shy",
1317
+ hopeful: "cheerful",
1318
+ proud: "excited",
1319
+ nostalgic: "thinking",
1320
+ relieved: "happy",
1321
+ anxious: "nervous",
1322
+ sarcastic: "sassy",
1323
+ devious: "sneaky",
1324
+ energetic: "hyper",
1325
+ wild: "crazy",
1326
+ exhausted: "sleepy",
1327
+ bored: "tired",
1328
+ cunning: "suspicious",
1329
+ judging: "mischievous",
1330
+ welcoming: "greeting",
1331
+ searching: "scanning",
1332
+ startled: "shocked",
1333
+ concentrated: "straining",
1334
+ amazed: "doubletake",
1335
+ glitchy: "glitching",
1336
+ glitch: "glitching"
1337
+ };
1338
+ function O(r) {
1339
+ return T[r] ? r : L[r] ?? null;
1340
+ }
1341
+ class z extends F {
1342
+ constructor(e = {}) {
1343
+ super();
1344
+ a(this, "_state");
1345
+ a(this, "_currentExpression", null);
1346
+ a(this, "_isPlaying", !1);
1347
+ a(this, "_isAutoAnimating", !1);
1348
+ a(this, "_isIdleMode", !1);
1349
+ a(this, "_mood", "normal");
1350
+ a(this, "timeouts", /* @__PURE__ */ new Set());
1351
+ // Loop sequence state
1352
+ a(this, "_loopingSeqId", null);
1353
+ // Idle fidget state
1354
+ a(this, "_idleFidgetActive", !1);
1355
+ a(this, "_idleFidgetTimer", null);
1356
+ a(this, "_idleFidgetOptions", {
1357
+ sequences: ["slowBlink", "curiousScan", "lookAroundScan", "shiftyEyes", "shyReaction", "winkSequence", "headTiltConfused", "sideEye", "doubleTake", "leanInCurious", "doubleBlink", "nervousGlance"],
1358
+ glitchSequences: ["glitchMood", "quirkyGlitch"],
1359
+ glitchChance: 0.15,
1360
+ minInterval: 3e3,
1361
+ maxInterval: 8e3
1362
+ });
1363
+ const i = e.initialExpression ?? "neutral", s = k(i);
1364
+ this._state = s ? s.state : this.getDefaultState(), this._currentExpression = i, this._mood = e.initialMood ?? "normal", e.autoAnimate && this.setAutoAnimate(!0), (e.idleMode ?? !0) && this.setIdleMode(!0);
1365
+ }
1366
+ // Getters for public state
1367
+ get state() {
1368
+ return Object.freeze({ ...this._state });
1369
+ }
1370
+ get currentExpression() {
1371
+ return this._currentExpression;
1372
+ }
1373
+ get isPlaying() {
1374
+ return this._isPlaying;
1375
+ }
1376
+ get isAutoAnimating() {
1377
+ return this._isAutoAnimating;
1378
+ }
1379
+ get isIdleMode() {
1380
+ return this._isIdleMode;
1381
+ }
1382
+ get mood() {
1383
+ return this._mood;
1384
+ }
1385
+ /**
1386
+ * Set the avatar's facial expression.
1387
+ *
1388
+ * @param expressionId - The ID of the expression to set
1389
+ * @param transitionMs - Optional transition duration (not used in MVP)
1390
+ * @throws {Error} If the expression ID is not found
1391
+ *
1392
+ * @example
1393
+ * ```typescript
1394
+ * controller.setExpression('happy');
1395
+ * controller.setExpression('surprised');
1396
+ * ```
1397
+ */
1398
+ setExpression(e, i) {
1399
+ const s = k(e);
1400
+ if (!s)
1401
+ throw new Error(`Expression "${e}" not found`);
1402
+ const o = { ...s.state };
1403
+ if (o.face) {
1404
+ const c = (Math.random() - 0.5) * 0.1, l = (Math.random() - 0.5) * 0.1, p = (Math.random() - 0.5) * 4;
1405
+ o.face = {
1406
+ x: o.face.x + c,
1407
+ y: o.face.y + l,
1408
+ tilt: (o.face.tilt ?? 0) + p
1409
+ };
1410
+ } else
1411
+ o.face = {
1412
+ x: (Math.random() - 0.5) * 0.12,
1413
+ y: (Math.random() - 0.5) * 0.12,
1414
+ tilt: (Math.random() - 0.5) * 5
1415
+ };
1416
+ this._state = o, this._currentExpression = e, this.emit("expressionChange", e), this.emit("stateChange", this._state);
1417
+ }
1418
+ /**
1419
+ * Set avatar state directly with partial updates.
1420
+ *
1421
+ * @param partialState - Partial state to merge with current state
1422
+ * @param transitionMs - Optional transition duration (not used in MVP)
1423
+ *
1424
+ * @example
1425
+ * ```typescript
1426
+ * controller.setState({ eyes: { scaleY: 0.5 } });
1427
+ * ```
1428
+ */
1429
+ setState(e, i) {
1430
+ this._state = {
1431
+ eyes: { ...this._state.eyes, ...e.eyes },
1432
+ mouth: { ...this._state.mouth, ...e.mouth },
1433
+ face: e.face ? { ...this._state.face, ...e.face } : this._state.face
1434
+ }, this._currentExpression = null, this.emit("stateChange", this._state);
1435
+ }
1436
+ /**
1437
+ * Trigger a blink animation.
1438
+ *
1439
+ * @param durationMs - Duration of the blink in milliseconds (default: 150)
1440
+ * @returns Promise that resolves when blink completes
1441
+ *
1442
+ * @example
1443
+ * ```typescript
1444
+ * await controller.blink();
1445
+ * await controller.blink(200); // Slower blink
1446
+ * ```
1447
+ */
1448
+ async blink(e = 150) {
1449
+ if (this._isPlaying) return;
1450
+ const i = { ...this._state }, s = this._currentExpression;
1451
+ this.setState({ eyes: { scaleY: 0.08 } }), await this.wait(e), this._state = i, this._currentExpression = s, this.emit("stateChange", this._state);
1452
+ }
1453
+ /**
1454
+ * Play an animation sequence.
1455
+ *
1456
+ * @param sequenceId - The ID of the sequence to play
1457
+ * @returns Promise that resolves when sequence completes
1458
+ * @throws {Error} If the sequence ID is not found
1459
+ *
1460
+ * @example
1461
+ * ```typescript
1462
+ * await controller.playSequence('greeting');
1463
+ * await controller.playSequence('cheerful');
1464
+ * ```
1465
+ */
1466
+ async playSequence(e) {
1467
+ const i = _(e);
1468
+ if (!i)
1469
+ throw new Error(`Sequence "${e}" not found`);
1470
+ this.stopSequence(), this._isPlaying = !0, this.emit("sequenceStart", e);
1471
+ try {
1472
+ await this.runSequence(i), this.emit("sequenceEnd", e);
1473
+ } finally {
1474
+ this._isPlaying = !1;
1475
+ }
1476
+ }
1477
+ /**
1478
+ * Stop the currently playing sequence.
1479
+ *
1480
+ * @example
1481
+ * ```typescript
1482
+ * controller.stopSequence();
1483
+ * ```
1484
+ */
1485
+ stopSequence() {
1486
+ this.timeouts.forEach(clearTimeout), this.timeouts.clear(), this._isPlaying = !1;
1487
+ }
1488
+ /**
1489
+ * Enable or disable auto-animation mode.
1490
+ *
1491
+ * @param enabled - Whether to enable auto-animation
1492
+ *
1493
+ * @example
1494
+ * ```typescript
1495
+ * controller.setAutoAnimate(true); // Start auto-animations
1496
+ * controller.setAutoAnimate(false); // Stop auto-animations
1497
+ * ```
1498
+ */
1499
+ setAutoAnimate(e) {
1500
+ this._isAutoAnimating = e, this.emit("autoAnimateChange", e), e ? (this.scheduleRandomBlink(), this.scheduleRandomCheerful()) : (this.stopSequence(), this.setExpression("neutral"));
1501
+ }
1502
+ /**
1503
+ * Set the avatar's mood (affects animation speed).
1504
+ *
1505
+ * @param mood - The mood to set ('normal', 'energized', or 'tired')
1506
+ *
1507
+ * @example
1508
+ * ```typescript
1509
+ * controller.setMood('energized'); // Faster animations
1510
+ * controller.setMood('tired'); // Slower animations
1511
+ * controller.setMood('normal'); // Default speed
1512
+ * ```
1513
+ */
1514
+ setMood(e) {
1515
+ this._mood = e, this.emit("moodChange", e);
1516
+ }
1517
+ /**
1518
+ * Enable or disable idle mode (random expressions and moods).
1519
+ *
1520
+ * @param enabled - Whether to enable idle mode
1521
+ *
1522
+ * @example
1523
+ * ```typescript
1524
+ * controller.setIdleMode(true); // Start random variations
1525
+ * controller.setIdleMode(false); // Stop random variations
1526
+ * ```
1527
+ */
1528
+ setIdleMode(e) {
1529
+ this._isIdleMode = e, this.emit("idleModeChange", e), e && (this.scheduleRandomExpression(), this.scheduleRandomMood());
1530
+ }
1531
+ /**
1532
+ * Get the current avatar state (read-only).
1533
+ *
1534
+ * @returns Frozen copy of current state
1535
+ */
1536
+ getState() {
1537
+ return Object.freeze({ ...this._state });
1538
+ }
1539
+ /**
1540
+ * Get the current expression ID.
1541
+ *
1542
+ * @returns Current expression ID or null if state was set directly
1543
+ */
1544
+ getCurrentExpression() {
1545
+ return this._currentExpression;
1546
+ }
1547
+ /**
1548
+ * Get all available expressions.
1549
+ *
1550
+ * @returns Array of expression configurations
1551
+ */
1552
+ getExpressions() {
1553
+ return B;
1554
+ }
1555
+ /**
1556
+ * Get all available sequences.
1557
+ *
1558
+ * @returns Array of sequence configurations
1559
+ */
1560
+ getSequences() {
1561
+ return W;
1562
+ }
1563
+ /**
1564
+ * Check if any animation is currently playing.
1565
+ *
1566
+ * @returns True if sequence is playing or auto-animation is enabled
1567
+ */
1568
+ isAnimating() {
1569
+ return this._isPlaying || this._isAutoAnimating;
1570
+ }
1571
+ /**
1572
+ * Play a sequence in a loop until stopLoop() is called.
1573
+ * Disables autoAnimate and idleMode while looping.
1574
+ */
1575
+ loopSequence(e) {
1576
+ this._loopingSeqId = e, this.setAutoAnimate(!1), this.setIdleMode(!1);
1577
+ const i = () => {
1578
+ this._loopingSeqId === e && this.playSequence(e).then(() => {
1579
+ this._loopingSeqId === e && i();
1580
+ }).catch(() => {
1581
+ });
1582
+ };
1583
+ i();
1584
+ }
1585
+ /**
1586
+ * Stop the currently looping sequence.
1587
+ */
1588
+ stopLoop() {
1589
+ this._loopingSeqId = null, this.stopSequence();
1590
+ }
1591
+ /**
1592
+ * Apply an emotion by key — resolves to expression or sequence via EMOTION_ACTIONS.
1593
+ * Returns the resolved key, or null if not found.
1594
+ */
1595
+ applyEmotion(e) {
1596
+ const i = O(e) ?? e, s = T[i];
1597
+ if (!s) return null;
1598
+ if (this.stopSequence(), _(s.id))
1599
+ this.playSequence(s.id).catch(() => {
1600
+ });
1601
+ else if (k(s.id))
1602
+ this.setExpression(s.id);
1603
+ else if (k(i))
1604
+ this.setExpression(i);
1605
+ else
1606
+ return null;
1607
+ return this.emit("emotionApplied", i), i;
1608
+ }
1609
+ /**
1610
+ * Enable/disable idle fidget — plays random sequences at random intervals.
1611
+ */
1612
+ setIdleFidget(e, i) {
1613
+ if (this._idleFidgetActive = !1, this._idleFidgetTimer && (clearTimeout(this._idleFidgetTimer), this._idleFidgetTimer = null), !e) return;
1614
+ i && (this._idleFidgetOptions = { ...this._idleFidgetOptions, ...i }), this._idleFidgetActive = !0;
1615
+ const s = this._idleFidgetOptions, o = () => {
1616
+ if (!this._idleFidgetActive) return;
1617
+ const c = s.minInterval + Math.random() * (s.maxInterval - s.minInterval);
1618
+ this._idleFidgetTimer = setTimeout(() => {
1619
+ if (!this._idleFidgetActive) return;
1620
+ const l = Math.random() < s.glitchChance ? s.glitchSequences : s.sequences, p = l[Math.floor(Math.random() * l.length)];
1621
+ this.emit("idleFidgetPlay", p), this.playSequence(p).then(() => {
1622
+ this._idleFidgetActive && o();
1623
+ }).catch(() => {
1624
+ this._idleFidgetActive && o();
1625
+ });
1626
+ }, c);
1627
+ };
1628
+ o();
1629
+ }
1630
+ /**
1631
+ * Clean up resources and stop all animations.
1632
+ * Call this when disposing of the controller.
1633
+ */
1634
+ destroy() {
1635
+ this.stopSequence(), this.stopLoop(), this.setIdleFidget(!1), this.setAutoAnimate(!1), this.removeAllListeners();
1636
+ }
1637
+ // Private helper methods
1638
+ async runSequence(e) {
1639
+ const i = async () => {
1640
+ for (const s of e.frames) {
1641
+ if (!this._isPlaying) break;
1642
+ s.expression ? this.setExpression(s.expression) : s.state && this.setState(s.state);
1643
+ const o = (s.transitionDuration ?? 0) + s.duration;
1644
+ await this.wait(o);
1645
+ }
1646
+ e.loop && this._isPlaying && await i();
1647
+ };
1648
+ await i();
1649
+ }
1650
+ scheduleRandomBlink() {
1651
+ if (!this._isAutoAnimating) return;
1652
+ const i = (2500 + Math.random() * 4e3) * Y[this._mood], s = setTimeout(async () => {
1653
+ if (!this._isPlaying) {
1654
+ const o = Math.random() < 0.3;
1655
+ await this.playSequence(o ? "doubleBlink" : "blink");
1656
+ }
1657
+ this.scheduleRandomBlink();
1658
+ }, i);
1659
+ this.timeouts.add(s);
1660
+ }
1661
+ scheduleRandomCheerful() {
1662
+ if (!this._isAutoAnimating) return;
1663
+ const i = (6e3 + Math.random() * 6e3) * Y[this._mood], s = setTimeout(async () => {
1664
+ this._isPlaying || await this.playSequence("cheerful"), this.scheduleRandomCheerful();
1665
+ }, i);
1666
+ this.timeouts.add(s);
1667
+ }
1668
+ scheduleRandomExpression() {
1669
+ if (!this._isIdleMode) return;
1670
+ const e = 8e3 + Math.random() * 7e3, i = setTimeout(() => {
1671
+ if (!this._isPlaying && this._isIdleMode) {
1672
+ const s = [
1673
+ "neutral",
1674
+ "happy",
1675
+ "cheerful",
1676
+ "curious",
1677
+ "skeptical",
1678
+ "shy",
1679
+ "squintLight",
1680
+ "squintMedium",
1681
+ "focused",
1682
+ "surprised"
1683
+ ], o = s[Math.floor(Math.random() * s.length)];
1684
+ this.setExpression(o);
1685
+ }
1686
+ this.scheduleRandomExpression();
1687
+ }, e);
1688
+ this.timeouts.add(i);
1689
+ }
1690
+ scheduleRandomMood() {
1691
+ if (!this._isIdleMode) return;
1692
+ const e = 2e4 + Math.random() * 2e4, i = setTimeout(() => {
1693
+ if (!this._isPlaying && this._isIdleMode) {
1694
+ const s = ["normal", "energized", "tired"], o = s[Math.floor(Math.random() * s.length)];
1695
+ this.setMood(o);
1696
+ }
1697
+ this.scheduleRandomMood();
1698
+ }, e);
1699
+ this.timeouts.add(i);
1700
+ }
1701
+ wait(e) {
1702
+ const i = e * Y[this._mood];
1703
+ return new Promise((s) => {
1704
+ const o = setTimeout(() => {
1705
+ this.timeouts.delete(o), s();
1706
+ }, i);
1707
+ this.timeouts.add(o);
1708
+ });
1709
+ }
1710
+ getDefaultState() {
1711
+ return {
1712
+ eyes: { scaleY: 1 },
1713
+ mouth: { visible: !1 },
1714
+ face: { x: 0, y: 0, tilt: 0 }
1715
+ };
1716
+ }
1717
+ }
1718
+ const Q = (r = {}) => {
1719
+ const t = v(null), [e, i] = g(() => ({ eyes: { scaleY: 1 }, mouth: { visible: !1 } })), [s, o] = g(
1720
+ r.initialExpression ?? "neutral"
1721
+ ), [c, l] = g(!1), [p, m] = g(r.autoAnimate ?? !1), [x, y] = g(r.idleMode ?? !0), [f, u] = g(r.initialMood ?? "normal");
1722
+ t.current || (t.current = new z(r));
1723
+ const n = t.current;
1724
+ return C(() => {
1725
+ const d = (h) => i(h), D = (h) => o(h), S = () => l(!0), q = () => l(!1), w = (h) => m(h), A = (h) => y(h), E = (h) => u(h);
1726
+ return n.on("stateChange", d), n.on("expressionChange", D), n.on("sequenceStart", S), n.on("sequenceEnd", q), n.on("autoAnimateChange", w), n.on("idleModeChange", A), n.on("moodChange", E), i(n.getState()), o(n.getCurrentExpression()), y(n.isIdleMode), u(n.mood), () => {
1727
+ n.off("stateChange", d), n.off("expressionChange", D), n.off("sequenceStart", S), n.off("sequenceEnd", q), n.off("autoAnimateChange", w), n.off("idleModeChange", A), n.off("moodChange", E);
1728
+ };
1729
+ }, [n]), C(() => () => n.destroy(), [n]), {
1730
+ state: e,
1731
+ currentExpression: s,
1732
+ isPlaying: c,
1733
+ isAutoAnimating: p,
1734
+ isIdleMode: x,
1735
+ mood: f,
1736
+ setExpression: n.setExpression.bind(n),
1737
+ setState: n.setState.bind(n),
1738
+ blink: n.blink.bind(n),
1739
+ playSequence: n.playSequence.bind(n),
1740
+ stopSequence: n.stopSequence.bind(n),
1741
+ setAutoAnimate: n.setAutoAnimate.bind(n),
1742
+ setIdleMode: n.setIdleMode.bind(n),
1743
+ setMood: n.setMood.bind(n),
1744
+ getExpressions: n.getExpressions.bind(n),
1745
+ getSequences: n.getSequences.bind(n)
1746
+ };
1747
+ };
1748
+ function P(r) {
1749
+ const t = r.toLowerCase().trim();
1750
+ if (!t) return "closed";
1751
+ const e = t[0], i = t.slice(0, 2);
1752
+ return "mbp".includes(e) ? "M" : "fv".includes(e) ? "F" : e === "w" || i === "oo" ? "W" : e === "o" ? "O" : e === "a" ? "A" : "ei".includes(e) ? "E" : e === "u" ? "U" : "A";
1753
+ }
1754
+ class j {
1755
+ constructor(t, e) {
1756
+ a(this, "controller");
1757
+ a(this, "options");
1758
+ a(this, "_isSpeaking", !1);
1759
+ // Viseme scheduling
1760
+ a(this, "timingOrigin", 0);
1761
+ a(this, "scheduledTimeouts", /* @__PURE__ */ new Set());
1762
+ // Amplitude analysis
1763
+ a(this, "audioContext", null);
1764
+ a(this, "analyser", null);
1765
+ a(this, "sourceNode", null);
1766
+ a(this, "amplitudeFrame", null);
1767
+ a(this, "amplitudeData", null);
1768
+ a(this, "ownsAudioContext", !1);
1769
+ a(this, "connectedElement", null);
1770
+ this.controller = t, this.options = {
1771
+ sensitivity: (e == null ? void 0 : e.sensitivity) ?? 0.5,
1772
+ smoothing: (e == null ? void 0 : e.smoothing) ?? 0.3,
1773
+ idleExpression: (e == null ? void 0 : e.idleExpression) ?? "neutral"
1774
+ };
1775
+ }
1776
+ get isSpeaking() {
1777
+ return this._isSpeaking;
1778
+ }
1779
+ /**
1780
+ * Feed word-level timestamps from TTS (e.g. Cartesia).
1781
+ * Schedules mouth shape changes relative to the timing origin set by start().
1782
+ */
1783
+ feedTimestamps(t) {
1784
+ const e = performance.now(), i = this.timingOrigin || e;
1785
+ for (const s of t) {
1786
+ const o = s.start * 1e3, c = s.end * 1e3, l = e - i, p = Math.max(0, o - l), m = setTimeout(() => {
1787
+ if (this.scheduledTimeouts.delete(m), !this._isSpeaking) return;
1788
+ const f = P(s.word);
1789
+ this.controller.setState({ mouth: { shape: f, visible: !0 } });
1790
+ }, p);
1791
+ this.scheduledTimeouts.add(m);
1792
+ const x = Math.max(0, c - l), y = setTimeout(() => {
1793
+ this.scheduledTimeouts.delete(y), this._isSpeaking && this.controller.setState({ mouth: { shape: "closed", openness: 0 } });
1794
+ }, x);
1795
+ this.scheduledTimeouts.add(y);
1796
+ }
1797
+ }
1798
+ /**
1799
+ * Connect to an HTMLAudioElement or HTMLMediaElement for amplitude analysis.
1800
+ */
1801
+ connectElement(t) {
1802
+ if (this.connectedElement === t && this.analyser) return;
1803
+ this.disconnectAudio(), this.connectedElement = t, this.ownsAudioContext = !0, this.audioContext = new AudioContext();
1804
+ const e = this.audioContext.createMediaElementSource(t);
1805
+ this.setupAnalyser(e, this.audioContext), e.connect(this.audioContext.destination), this.sourceNode = e;
1806
+ }
1807
+ /**
1808
+ * Connect to an existing AudioNode for amplitude analysis.
1809
+ */
1810
+ connectSource(t, e) {
1811
+ this.disconnectAudio(), this.ownsAudioContext = !1, this.audioContext = e, this.setupAnalyser(t, e), this.sourceNode = t;
1812
+ }
1813
+ /**
1814
+ * Start speech animation. Sets the timing origin for viseme scheduling.
1815
+ */
1816
+ start() {
1817
+ var t;
1818
+ this._isSpeaking = !0, this.timingOrigin = performance.now(), ((t = this.audioContext) == null ? void 0 : t.state) === "suspended" && this.audioContext.resume(), this.analyser && this.startAmplitudeLoop();
1819
+ }
1820
+ /**
1821
+ * Stop speech animation and reset mouth to closed.
1822
+ */
1823
+ stop() {
1824
+ this._isSpeaking = !1;
1825
+ for (const t of this.scheduledTimeouts)
1826
+ clearTimeout(t);
1827
+ this.scheduledTimeouts.clear(), this.amplitudeFrame !== null && (cancelAnimationFrame(this.amplitudeFrame), this.amplitudeFrame = null), this.controller.setState({
1828
+ mouth: { shape: "closed", openness: 0, visible: !1 }
1829
+ });
1830
+ }
1831
+ /**
1832
+ * Clean up all resources. Call when done with the controller.
1833
+ */
1834
+ destroy() {
1835
+ this.stop(), this.disconnectAudio();
1836
+ }
1837
+ // --- Private ---
1838
+ setupAnalyser(t, e) {
1839
+ this.analyser = e.createAnalyser(), this.analyser.fftSize = 256, this.analyser.smoothingTimeConstant = this.options.smoothing, t.connect(this.analyser), this.amplitudeData = new Uint8Array(this.analyser.frequencyBinCount);
1840
+ }
1841
+ startAmplitudeLoop() {
1842
+ if (this.amplitudeFrame !== null) return;
1843
+ const t = () => {
1844
+ if (!this._isSpeaking || !this.analyser || !this.amplitudeData) {
1845
+ this.amplitudeFrame = null;
1846
+ return;
1847
+ }
1848
+ this.analyser.getByteTimeDomainData(this.amplitudeData);
1849
+ let e = 0;
1850
+ for (let c = 0; c < this.amplitudeData.length; c++) {
1851
+ const l = (this.amplitudeData[c] - 128) / 128;
1852
+ e += l * l;
1853
+ }
1854
+ const i = Math.sqrt(e / this.amplitudeData.length), s = Math.min(1, i * (4 + this.options.sensitivity * 12)), o = s < 0.08 ? 0 : s;
1855
+ this.controller.setState({
1856
+ mouth: { openness: o, visible: o > 0 }
1857
+ }), this.amplitudeFrame = requestAnimationFrame(t);
1858
+ };
1859
+ this.amplitudeFrame = requestAnimationFrame(t);
1860
+ }
1861
+ disconnectAudio() {
1862
+ this.amplitudeFrame !== null && (cancelAnimationFrame(this.amplitudeFrame), this.amplitudeFrame = null), this.analyser && (this.analyser.disconnect(), this.analyser = null), this.ownsAudioContext && this.audioContext && this.audioContext.close().catch(() => {
1863
+ }), this.audioContext = null, this.sourceNode = null, this.connectedElement = null, this.amplitudeData = null;
1864
+ }
1865
+ }
1866
+ class H {
1867
+ constructor(t) {
1868
+ a(this, "words", []);
1869
+ a(this, "processedCount", 0);
1870
+ a(this, "groups", []);
1871
+ a(this, "minWords");
1872
+ a(this, "maxWords");
1873
+ this.minWords = (t == null ? void 0 : t.minWords) ?? 2, this.maxWords = (t == null ? void 0 : t.maxWords) ?? 5;
1874
+ }
1875
+ /** Feed new word timestamps (accumulates). */
1876
+ feed(t) {
1877
+ this.words.push(...t), this.process();
1878
+ }
1879
+ /** Flush any remaining un-grouped words into a final group. */
1880
+ flush() {
1881
+ if (this.processedCount < this.words.length) {
1882
+ const t = this.words.slice(this.processedCount);
1883
+ this.groups.push({
1884
+ text: t.map((e) => e.word).join(" "),
1885
+ startTime: t[0].start,
1886
+ endTime: t[t.length - 1].end
1887
+ }), this.processedCount = this.words.length;
1888
+ }
1889
+ }
1890
+ /** Get all bubble groups computed so far. */
1891
+ getGroups() {
1892
+ return this.groups;
1893
+ }
1894
+ /** Get accumulated words. */
1895
+ getWords() {
1896
+ return this.words;
1897
+ }
1898
+ /** Reset all state for a new utterance. */
1899
+ reset() {
1900
+ this.words = [], this.processedCount = 0, this.groups = [];
1901
+ }
1902
+ process() {
1903
+ for (; this.processedCount < this.words.length; ) {
1904
+ const t = this.processedCount;
1905
+ let e = t;
1906
+ for (; e < this.words.length; ) {
1907
+ e++;
1908
+ const s = e - t, o = this.words[e - 1].word;
1909
+ if (/[.!?;]$/.test(o) && s >= this.minWords || s >= this.maxWords) break;
1910
+ }
1911
+ if (e - t < this.minWords && e < this.words.length || e === this.words.length && e - t < this.minWords) break;
1912
+ const i = this.words.slice(t, e);
1913
+ this.groups.push({
1914
+ text: i.map((s) => s.word).join(" "),
1915
+ startTime: i[0].start,
1916
+ endTime: i[i.length - 1].end
1917
+ }), this.processedCount = e;
1918
+ }
1919
+ }
1920
+ }
1921
+ function N(r) {
1922
+ const t = r.toLowerCase().replace(/[^a-z]/g, "");
1923
+ if (!t) return "closed";
1924
+ const e = t[0];
1925
+ return "mbp".includes(e) ? "M" : "fv".includes(e) ? "F" : e === "w" || t.startsWith("oo") ? "W" : e === "o" ? "O" : e === "a" ? "A" : "ei".includes(e) ? "E" : e === "u" ? "U" : "A";
1926
+ }
1927
+ class V {
1928
+ constructor(t, e, i, s, o) {
1929
+ a(this, "controller");
1930
+ a(this, "bubbleGrouper");
1931
+ a(this, "getPlaybackPosition");
1932
+ a(this, "callbacks");
1933
+ a(this, "rafId", 0);
1934
+ a(this, "active", !1);
1935
+ a(this, "lastBubbleIdx", -1);
1936
+ a(this, "lastVisemeWordIdx", -1);
1937
+ // Expression sync — each queued expression has a key + non-whitespace char index
1938
+ a(this, "exprQueue", []);
1939
+ a(this, "lastAppliedExprQueueIdx", -1);
1940
+ a(this, "flushTimer", null);
1941
+ a(this, "tick", () => {
1942
+ var c, l, p, m, x, y, f;
1943
+ if (!this.active) return;
1944
+ const t = this.getPlaybackPosition(), e = this.bubbleGrouper.getWords();
1945
+ let i = -1;
1946
+ for (let u = e.length - 1; u >= 0; u--)
1947
+ if (e[u].start <= t) {
1948
+ i = u;
1949
+ break;
1950
+ }
1951
+ if (i >= 0 && i !== this.lastVisemeWordIdx) {
1952
+ this.lastVisemeWordIdx = i;
1953
+ const u = e[i];
1954
+ t <= u.end ? this.controller.setState({ mouth: { shape: N(u.word) } }) : this.controller.setState({ mouth: { shape: "closed" } });
1955
+ } else if (i >= 0) {
1956
+ const u = e[i];
1957
+ t > u.end && this.controller.setState({ mouth: { shape: "closed" } });
1958
+ }
1959
+ const s = this.bubbleGrouper.getGroups();
1960
+ let o = -1;
1961
+ for (let u = s.length - 1; u >= 0; u--)
1962
+ if (s[u].startTime <= t) {
1963
+ o = u;
1964
+ break;
1965
+ }
1966
+ if (o >= 0 && o !== this.lastBubbleIdx && (this.lastBubbleIdx = o, (l = (c = this.callbacks).onBubbleChange) == null || l.call(c, s[o].text, !0)), o >= 0 && t > s[o].endTime && ((m = (p = this.callbacks).onBubbleChange) == null || m.call(p, "", !1)), this.exprQueue.length > 0 && i >= 0) {
1967
+ let u = 0;
1968
+ for (let d = 0; d <= i; d++)
1969
+ u += e[d].word.length;
1970
+ let n = -1;
1971
+ for (let d = this.exprQueue.length - 1; d >= 0; d--)
1972
+ if (this.exprQueue[d].nwsCharIdx <= u) {
1973
+ n = d;
1974
+ break;
1975
+ }
1976
+ if (n >= 0 && n !== this.lastAppliedExprQueueIdx) {
1977
+ this.lastAppliedExprQueueIdx = n;
1978
+ const d = this.exprQueue[n], D = ((x = e[i]) == null ? void 0 : x.word) ?? "?";
1979
+ console.log("[PlaybackSync] Apply expression:", d.key, "| word #" + i, "(" + D + ")", "| spokenNwsChars:", u, "| expr nwsCharIdx:", d.nwsCharIdx), (f = (y = this.callbacks).onExpressionChange) == null || f.call(y, d.key);
1980
+ }
1981
+ }
1982
+ this.rafId = requestAnimationFrame(this.tick);
1983
+ });
1984
+ this.controller = t, this.bubbleGrouper = e, this.getPlaybackPosition = i, this.callbacks = s ?? {};
1985
+ }
1986
+ /** Queue an expression key with the non-whitespace char index it should activate at. */
1987
+ queueExpression(t, e) {
1988
+ console.log("[PlaybackSync] Queue expression:", t, "nwsCharIdx:", e), this.exprQueue.push({ key: t, nwsCharIdx: e });
1989
+ }
1990
+ /** Feed word timestamps — delegates to bubbleGrouper and detects expression breaks. */
1991
+ feedTimestamps(t) {
1992
+ this.bubbleGrouper.getWords().length;
1993
+ const i = [];
1994
+ for (let s = 0; s < t.words.length; s++)
1995
+ i.push({ word: t.words[s], start: t.start[s], end: t.end[s] });
1996
+ this.bubbleGrouper.feed(i), this.flushTimer && clearTimeout(this.flushTimer), this.flushTimer = setTimeout(() => this.bubbleGrouper.flush(), 500);
1997
+ }
1998
+ /** Start the rAF sync loop. */
1999
+ start() {
2000
+ this.active || (this.active = !0, this.lastBubbleIdx = -1, this.lastVisemeWordIdx = -1, this.lastAppliedExprQueueIdx = -1, this.tick());
2001
+ }
2002
+ /** Stop the sync loop and reset state. */
2003
+ stop() {
2004
+ var t, e;
2005
+ this.active = !1, cancelAnimationFrame(this.rafId), this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = null), this.exprQueue = [], this.lastAppliedExprQueueIdx = -1, this.lastBubbleIdx = -1, this.lastVisemeWordIdx = -1, this.bubbleGrouper.reset(), (e = (t = this.callbacks).onBubbleChange) == null || e.call(t, "", !1);
2006
+ }
2007
+ /** Whether the sync loop is currently active. */
2008
+ get isActive() {
2009
+ return this.active;
2010
+ }
2011
+ }
2012
+ export {
2013
+ z as A,
2014
+ T as E,
2015
+ L as F,
2016
+ V as P,
2017
+ H as S,
2018
+ j as a,
2019
+ b,
2020
+ k as c,
2021
+ _ as d,
2022
+ B as e,
2023
+ M as f,
2024
+ U as g,
2025
+ O as r,
2026
+ W as s,
2027
+ Q as u,
2028
+ P as w
2029
+ };