dsh-notch 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/BASELINE.md +13 -0
  2. package/DESIGN.md +34 -0
  3. package/LICENSE +21 -0
  4. package/README.en.md +136 -0
  5. package/README.md +145 -0
  6. package/cordis.yml +5 -0
  7. package/docs/motion-audit.md +65 -0
  8. package/docs/motion-continuity.md +77 -0
  9. package/docs/releases/v0.3.0.md +20 -0
  10. package/dshx.yml +14 -0
  11. package/macos/Package.swift +17 -0
  12. package/macos/STATUS-MOTION.md +49 -0
  13. package/macos/Sources/Client.swift +139 -0
  14. package/macos/Sources/IdleRobot.swift +423 -0
  15. package/macos/Sources/NotchMarkdown.swift +266 -0
  16. package/macos/Sources/Panel.swift +153 -0
  17. package/macos/Sources/Resources/Idle/balance.json +1 -0
  18. package/macos/Sources/Resources/Idle/blink.json +1 -0
  19. package/macos/Sources/Resources/Idle/cube-in.json +1 -0
  20. package/macos/Sources/Resources/Idle/dance.json +1 -0
  21. package/macos/Sources/Resources/Idle/hop.json +1 -0
  22. package/macos/Sources/Resources/Idle/nod.json +1 -0
  23. package/macos/Sources/Resources/Idle/satellite-in.json +1 -0
  24. package/macos/Sources/Resources/Idle/satellite-out.json +1 -0
  25. package/macos/Sources/Resources/Idle/scan.json +1 -0
  26. package/macos/Sources/Resources/Idle/sleep.json +1 -0
  27. package/macos/Sources/Resources/Idle/sneeze.json +1 -0
  28. package/macos/Sources/Resources/Idle/stretch.json +1 -0
  29. package/macos/Sources/Resources/Idle/tilt.json +1 -0
  30. package/macos/Sources/ReviewStudio.swift +337 -0
  31. package/macos/Sources/RootView.swift +1093 -0
  32. package/macos/Sources/StatusOrbit.swift +642 -0
  33. package/macos/Sources/main.swift +153 -0
  34. package/macos/Tests/ExpandedHeightProbe.swift +108 -0
  35. package/macos/Tests/GeometryProbe.swift +98 -0
  36. package/macos/Tests/IdleProbe.swift +294 -0
  37. package/macos/Tests/MotionProbe.swift +106 -0
  38. package/macos/Tests/OutcomeProbe.swift +133 -0
  39. package/macos/Tests/ScrollbarProbe.swift +91 -0
  40. package/macos/Tests/expanded-height.sh +13 -0
  41. package/macos/Tests/geometry.sh +14 -0
  42. package/macos/Tests/idle.sh +14 -0
  43. package/macos/Tests/motion.sh +14 -0
  44. package/macos/Tests/outcome.sh +14 -0
  45. package/macos/Tests/scrollbar.sh +13 -0
  46. package/package.json +46 -0
  47. package/src/board.ts +299 -0
  48. package/src/browse-sync.ts +59 -0
  49. package/src/dsh-notch.ts +60 -0
  50. package/src/http.ts +215 -0
  51. package/src/session-state.ts +31 -0
  52. package/src/store.ts +49 -0
  53. package/src/types.ts +65 -0
  54. package/tests/answer-lifecycle.test.mjs +23 -0
  55. package/tests/board.test.mjs +7 -0
  56. package/tests/browse-sync.test.mjs +20 -0
  57. package/tests/mirror.test.mjs +4 -0
  58. package/tests/peer-range.test.mjs +21 -0
  59. package/tests/session-state.test.mjs +54 -0
  60. package/tests/unread-hold.test.mjs +90 -0
  61. package/tools/desktop-shell/README.md +56 -0
  62. package/tools/desktop-shell/child-log.mjs +6 -0
  63. package/tools/desktop-shell/child-log.test.mjs +14 -0
  64. package/tools/desktop-shell/package-recovery.py +78 -0
  65. package/tools/desktop-shell/recovery-probe.cjs +50 -0
  66. package/tools/desktop-shell/renderer-recovery.mjs +95 -0
  67. package/tools/desktop-shell/renderer-recovery.test.mjs +24 -0
  68. package/tools/diagnostics/completion-watch.py +101 -0
  69. package/tools/diagnostics/completion-watch.test.py +17 -0
  70. package/tools/diagnostics/renderer-diagnostics.mjs +56 -0
  71. package/tools/diagnostics/renderer-diagnostics.test.mjs +32 -0
  72. package/tools/idle/LICENSE.open-bot-motion +21 -0
  73. package/tools/idle/export.mjs +40 -0
  74. package/tools/idle/index.html +4 -0
  75. package/tools/idle/motions.js +83 -0
  76. package/tools/idle/original.js +1673 -0
  77. package/tools/idle/renderer.js +1673 -0
  78. package/tools/recording/Client.swift +76 -0
  79. package/tools/recording/DemoCatalog.swift +174 -0
  80. package/tools/recording/README.md +23 -0
  81. package/tools/recording/build.sh +41 -0
  82. package/tools/recording/main.swift +100 -0
  83. package/tools/review/ReviewStudio.swift +236 -0
  84. package/tools/review/audit.sh +19 -0
  85. package/tools/review/build.sh +7 -0
  86. package/tools/review/main.swift +158 -0
  87. package/tools/review/render-audit.swift +32 -0
@@ -0,0 +1,1673 @@
1
+ /**
2
+ * OpenBotMotion v1.0.0
3
+ * Pure Code-Driven Zero-Dependency SVG Mascot Animation Engine (#1 ~ #7)
4
+ *
5
+ * @license MIT
6
+ * @author Open Bot Motion Team
7
+ */
8
+
9
+ (function (root, factory) {
10
+ if (typeof define === 'function' && define.amd) {
11
+ define([], factory);
12
+ } else if (typeof module === 'object' && module.exports) {
13
+ module.exports = factory();
14
+ } else {
15
+ root.OpenBotMotion = factory();
16
+ }
17
+ }(typeof self !== 'undefined' ? self : this, function () {
18
+ 'use strict';
19
+
20
+ var CHARCOAL = '#222126';
21
+ var EYE = '#ffffff';
22
+ var LOOP = 20.783;
23
+
24
+ /* =========================================================================
25
+ 1. Motion Primitives & Math
26
+ ========================================================================= */
27
+ var Easings = {
28
+ linear: function (p) { return p; },
29
+ easeInOutQuad: function (p) {
30
+ return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
31
+ },
32
+ easeOutCubic: function (p) {
33
+ return 1 - Math.pow(1 - p, 3);
34
+ },
35
+ easeInCubic: function (p) {
36
+ return p * p * p;
37
+ },
38
+ easeInOutCubic: function (p) {
39
+ return p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2;
40
+ },
41
+ springBouncy: function (p) {
42
+ if (p <= 0) return 0;
43
+ if (p >= 1) return 1;
44
+ var c4 = (2 * Math.PI) / 3;
45
+ return Math.pow(2, -10 * p) * Math.sin((p * 10 - 0.75) * c4) + 1;
46
+ }
47
+ };
48
+
49
+ function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
50
+ function lerp(a, b, t) { return a + (b - a) * t; }
51
+
52
+ function PoseTimeline(keyframes, options) {
53
+ this.options = Object.assign({
54
+ shortestArc: true,
55
+ shortestArcAngles: ['yaw', 'pitch', 'roll']
56
+ }, options || {});
57
+
58
+ var self = this;
59
+ this.keyframes = keyframes.map(function (kf) {
60
+ return {
61
+ t: kf.t,
62
+ label: kf.label || '',
63
+ ease: kf.ease || 'easeInOutQuad',
64
+ shortestArc: kf.shortestArc !== undefined ? kf.shortestArc : self.options.shortestArc,
65
+ pose: Object.assign({}, kf.pose)
66
+ };
67
+ }).sort(function (a, b) { return a.t - b.t; });
68
+
69
+ if (this.options.shortestArc) {
70
+ var angleKeys = this.options.shortestArcAngles;
71
+ for (var k = 0; k < angleKeys.length; k++) {
72
+ var key = angleKeys[k];
73
+ var prevVal = null;
74
+ for (var i = 0; i < this.keyframes.length; i++) {
75
+ var kf = this.keyframes[i];
76
+ if (kf.pose && typeof kf.pose[key] === 'number') {
77
+ if (prevVal !== null && kf.shortestArc !== false) {
78
+ var diff = kf.pose[key] - prevVal;
79
+ if (Math.abs(Math.abs(diff) - Math.PI) > 1e-4) {
80
+ var shortDiff = ((diff + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
81
+ kf.pose[key] = prevVal + shortDiff;
82
+ }
83
+ }
84
+ prevVal = kf.pose[key];
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ PoseTimeline.prototype.evaluate = function (t) {
92
+ var kfs = this.keyframes;
93
+ if (t <= kfs[0].t) {
94
+ return { pose: Object.assign({}, kfs[0].pose), label: kfs[0].label || '' };
95
+ }
96
+ if (t >= kfs[kfs.length - 1].t) {
97
+ var last = kfs[kfs.length - 1];
98
+ return { pose: Object.assign({}, last.pose), label: last.label || '' };
99
+ }
100
+
101
+ for (var i = 0; i < kfs.length - 1; i++) {
102
+ var k0 = kfs[i];
103
+ var k1 = kfs[i + 1];
104
+ if (t >= k0.t && t <= k1.t) {
105
+ var dur = k1.t - k0.t;
106
+ var p = dur > 0 ? (t - k0.t) / dur : 1.0;
107
+ var easeFn = Easings[k1.ease || 'easeInOutQuad'] || Easings.easeInOutQuad;
108
+ var ep = easeFn(clamp(p, 0, 1));
109
+
110
+ var res = {};
111
+ var p0 = k0.pose;
112
+ var p1 = k1.pose;
113
+ var allKeys = new Set(Object.keys(p0).concat(Object.keys(p1)));
114
+ allKeys.forEach(function (key) {
115
+ var isScaleKey = key.indexOf('scale') !== -1 || key.indexOf('Scale') !== -1;
116
+ var defVal = isScaleKey ? 1.0 : 0.0;
117
+ var v0 = p0[key] !== undefined ? p0[key] : (p1[key] !== undefined ? p1[key] : defVal);
118
+ var v1 = p1[key] !== undefined ? p1[key] : (p0[key] !== undefined ? p0[key] : defVal);
119
+ if (typeof v1 === 'number' && typeof v0 === 'number') {
120
+ res[key] = lerp(v0, v1, ep);
121
+ } else if (key === 'visible') {
122
+ res[key] = Boolean(v0 || v1);
123
+ } else {
124
+ res[key] = p >= 0.5 ? v1 : v0;
125
+ }
126
+ });
127
+ return { pose: res, label: k1.label || k0.label || '' };
128
+ }
129
+ }
130
+ return { pose: Object.assign({}, kfs[0].pose), label: '' };
131
+ };
132
+
133
+ function BlinkTrack(blinks) {
134
+ this.blinks = blinks || [];
135
+ }
136
+
137
+ BlinkTrack.prototype.evaluate = function (t) {
138
+ for (var i = 0; i < this.blinks.length; i++) {
139
+ var b = this.blinks[i];
140
+ if (t >= b.start && t <= b.start + b.duration) {
141
+ var p = (t - b.start) / b.duration;
142
+ return (b.maxSquint || 0.95) * Math.sin(p * Math.PI);
143
+ }
144
+ }
145
+ return 0.0;
146
+ };
147
+
148
+ function DotGridRig(spacing, baseRadius) {
149
+ this.spacing = spacing !== undefined ? spacing : 38.0;
150
+ this.baseRadius = baseRadius !== undefined ? baseRadius : 10.1;
151
+ }
152
+
153
+ DotGridRig.prototype.evaluate = function (t, botScale, botVisible) {
154
+ var dots = [];
155
+ var phase = t * Math.PI * 2 * 0.9 + 0.66;
156
+ var cubeHalf = botVisible ? 68.0 * (botScale || 0.0) : 0.0;
157
+
158
+ for (var row = -1; row <= 1; row++) {
159
+ for (var col = -1; col <= 1; col++) {
160
+ var diag = (col + 1) + (row + 1);
161
+ var wave = Math.cos(phase - diag * 0.65);
162
+ var r = this.baseRadius + 3.25 * wave;
163
+ var x = col * this.spacing;
164
+ var y = row * this.spacing;
165
+
166
+ var distBox = Math.max(Math.abs(x), Math.abs(y));
167
+ var visible = true;
168
+
169
+ if (botVisible && botScale > 0.05) {
170
+ if (distBox < cubeHalf - 4.0) {
171
+ visible = false;
172
+ r = 0.0;
173
+ } else if (distBox < cubeHalf + 10.0) {
174
+ var fade = (distBox - (cubeHalf - 4.0)) / 14.0;
175
+ r *= Math.max(0.0, Math.min(1.0, fade));
176
+ if (r < 0.5) visible = false;
177
+ }
178
+ }
179
+ dots.push({ x: x, y: y, r: r, visible: visible });
180
+ }
181
+ }
182
+ return dots;
183
+ };
184
+
185
+ /* =========================================================================
186
+ 2. SVG 3D Projector (Minkowski Convex Hull + Face Adherence)
187
+ ========================================================================= */
188
+ function SvgProjector(options) {
189
+ options = options || {};
190
+ this.fov = (options.fov || 28.0) * Math.PI / 180.0;
191
+ this.focalLength = 1.0 / Math.tan(this.fov / 2.0);
192
+ this.camPos = options.cameraPosition || [-0.32, 0.40, 2.70];
193
+ this.camTarget = options.cameraTarget || [0.0, 0.0, 0.0];
194
+ this.viewportSize = options.viewportSize || 280;
195
+
196
+ var c = this.camPos;
197
+ var tgt = this.camTarget;
198
+ var dist = Math.hypot(tgt[0] - c[0], tgt[1] - c[1], tgt[2] - c[2]);
199
+ this.fwd = [(tgt[0] - c[0]) / dist, (tgt[1] - c[1]) / dist, (tgt[2] - c[2]) / dist];
200
+
201
+ var up = [0, 1, 0];
202
+ var rx = this.fwd[1] * up[2] - this.fwd[2] * up[1];
203
+ var ry = this.fwd[2] * up[0] - this.fwd[0] * up[2];
204
+ var rz = this.fwd[0] * up[1] - this.fwd[1] * up[0];
205
+ var rlen = Math.hypot(rx, ry, rz);
206
+ this.right = [rx / rlen, ry / rlen, rz / rlen];
207
+
208
+ var ux = this.right[1] * this.fwd[2] - this.right[2] * this.fwd[1];
209
+ var uy = this.right[2] * this.fwd[0] - this.right[0] * this.fwd[2];
210
+ var uz = this.right[0] * this.fwd[1] - this.right[1] * this.fwd[0];
211
+ this.camUp = [ux, uy, uz];
212
+
213
+ this.halfSize = this.viewportSize / 2.0;
214
+ }
215
+
216
+ SvgProjector.prototype.projectWorldPoint = function (wx, wy, wz) {
217
+ var dx = wx - this.camPos[0];
218
+ var dy = wy - this.camPos[1];
219
+ var dz = wz - this.camPos[2];
220
+
221
+ var xc = dx * this.right[0] + dy * this.right[1] + dz * this.right[2];
222
+ var yc = dx * this.camUp[0] + dy * this.camUp[1] + dz * this.camUp[2];
223
+ var zc = dx * this.fwd[0] + dy * this.fwd[1] + dz * this.fwd[2];
224
+
225
+ if (zc <= 0.001) return null;
226
+ var xs = (xc / zc) * this.focalLength * this.halfSize;
227
+ var ys = -(yc / zc) * this.focalLength * this.halfSize;
228
+ return { x: xs, y: ys, z: zc };
229
+ };
230
+
231
+ SvgProjector.getConvexHull = function (pts) {
232
+ if (pts.length <= 1) return pts.slice();
233
+ var sorted = pts.slice().sort(function (a, b) {
234
+ return Math.abs(a.x - b.x) < 1e-5 ? a.y - b.y : a.x - b.x;
235
+ });
236
+ var cross = function (o, a, b) {
237
+ return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
238
+ };
239
+
240
+ var lower = [];
241
+ for (var i = 0; i < sorted.length; i++) {
242
+ var p = sorted[i];
243
+ while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 1e-5) {
244
+ lower.pop();
245
+ }
246
+ lower.push(p);
247
+ }
248
+
249
+ var upper = [];
250
+ for (var j = sorted.length - 1; j >= 0; j--) {
251
+ var q = sorted[j];
252
+ while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], q) <= 1e-5) {
253
+ upper.pop();
254
+ }
255
+ upper.push(q);
256
+ }
257
+
258
+ lower.pop();
259
+ upper.pop();
260
+ var hull = lower.concat(upper);
261
+
262
+ var area = 0;
263
+ for (var k = 0; k < hull.length; k++) {
264
+ var next = (k + 1) % hull.length;
265
+ area += hull[k].x * hull[next].y - hull[next].x * hull[k].y;
266
+ }
267
+ if (area < 0) hull.reverse();
268
+ return hull;
269
+ };
270
+
271
+ SvgProjector.prototype.projectRoundedCube = function (pose, cubeW, cubeRadius) {
272
+ cubeW = cubeW !== undefined ? cubeW : 1.0;
273
+ cubeRadius = cubeRadius !== undefined ? cubeRadius : 0.35;
274
+
275
+ if (!pose || pose.visible === false || (pose.scale !== undefined && pose.scale <= 0.001)) {
276
+ return { visible: false, bodyPath: '', eyes: [] };
277
+ }
278
+
279
+ var s = (pose.scale !== undefined ? pose.scale : 1.0) * 0.60;
280
+ var scX = pose.scaleX !== undefined ? pose.scaleX : 1.0;
281
+ var scY = pose.scaleY !== undefined ? pose.scaleY : 1.0;
282
+ var scZ = pose.scaleZ !== undefined ? pose.scaleZ : 1.0;
283
+
284
+ var hw = (cubeW / 2.0) * s * scX;
285
+ var hh = (cubeW / 2.0) * s * scY;
286
+ var hd = (cubeW / 2.0) * s * scZ;
287
+ var r = cubeRadius * s * Math.min(scX, scY);
288
+
289
+ var ihw = Math.max(0.001, hw - r);
290
+ var ihh = Math.max(0.001, hh - r);
291
+ var ihd = Math.max(0.001, hd - r);
292
+
293
+ var pitch = pose.pitch || 0.0;
294
+ var yaw = pose.yaw || 0.0;
295
+ var roll = pose.roll || 0.0;
296
+
297
+ var cx = Math.cos(pitch), sx = Math.sin(pitch);
298
+ var cy = Math.cos(yaw), sy = Math.sin(yaw);
299
+ var cz = Math.cos(roll), sz = Math.sin(roll);
300
+
301
+ var r00 = cz * cy;
302
+ var r01 = cz * sy * sx - sz * cx;
303
+ var r02 = cz * sy * cx + sz * sx;
304
+
305
+ var r10 = sz * cy;
306
+ var r11 = sz * sy * sx + cz * cx;
307
+ var r12 = sz * sy * cx - cz * sx;
308
+
309
+ var r20 = -sy;
310
+ var r21 = cy * sx;
311
+ var r22 = cy * cx;
312
+
313
+ var bx = pose.x || 0.0;
314
+ var by = pose.y || 0.0;
315
+ var bz = pose.z || 0.0;
316
+
317
+ var signs = [
318
+ [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1],
319
+ [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1]
320
+ ];
321
+
322
+ var projPts = [];
323
+ var avgZ = 0;
324
+
325
+ for (var idx = 0; idx < signs.length; idx++) {
326
+ var sgn = signs[idx];
327
+ var vx = sgn[0] * ihw;
328
+ var vy = sgn[1] * ihh;
329
+ var vz = sgn[2] * ihd;
330
+
331
+ var wx = bx + (r00 * vx + r01 * vy + r02 * vz);
332
+ var wy = by + (r10 * vx + r11 * vy + r12 * vz);
333
+ var wz = bz + (r20 * vx + r21 * vy + r22 * vz);
334
+
335
+ var p = this.projectWorldPoint(wx, wy, wz);
336
+ if (!p) continue;
337
+ projPts.push(p);
338
+ avgZ += p.z;
339
+ }
340
+
341
+ if (projPts.length < 4) {
342
+ return { visible: false, bodyPath: '', eyes: [] };
343
+ }
344
+
345
+ avgZ /= projPts.length;
346
+ var rScreen = (r / avgZ) * this.focalLength * this.halfSize;
347
+
348
+ var hull = SvgProjector.getConvexHull(projPts);
349
+ if (hull.length < 3) {
350
+ return { visible: false, bodyPath: '', eyes: [] };
351
+ }
352
+
353
+ var m = hull.length;
354
+ var segments = [];
355
+ for (var k = 0; k < m; k++) {
356
+ var p0 = hull[k];
357
+ var p1 = hull[(k + 1) % m];
358
+ var dx = p1.x - p0.x;
359
+ var dy = p1.y - p0.y;
360
+ var len = Math.hypot(dx, dy);
361
+ if (len < 1e-5) continue;
362
+
363
+ var nx = dy / len;
364
+ var ny = -dx / len;
365
+
366
+ var a = { x: p0.x + rScreen * nx, y: p0.y + rScreen * ny };
367
+ var b = { x: p1.x + rScreen * nx, y: p1.y + rScreen * ny };
368
+ segments.push({ a: a, b: b, corner: p1 });
369
+ }
370
+
371
+ var bodyPath = '';
372
+ if (segments.length >= 3) {
373
+ var d = [];
374
+ for (var si = 0; si < segments.length; si++) {
375
+ var seg = segments[si];
376
+ var nextSeg = segments[(si + 1) % segments.length];
377
+ if (si === 0) {
378
+ d.push('M ' + seg.a.x.toFixed(2) + ' ' + seg.a.y.toFixed(2));
379
+ }
380
+ d.push('L ' + seg.b.x.toFixed(2) + ' ' + seg.b.y.toFixed(2));
381
+ d.push('A ' + rScreen.toFixed(2) + ' ' + rScreen.toFixed(2) + ' 0 0 1 ' + nextSeg.a.x.toFixed(2) + ' ' + nextSeg.a.y.toFixed(2));
382
+ }
383
+ d.push('Z');
384
+ bodyPath = d.join(' ');
385
+ }
386
+
387
+ var eyes = [];
388
+ var fnx = r02;
389
+ var fny = r12;
390
+ var fnz = r22;
391
+ var viewDot = -(fnx * this.fwd[0] + fny * this.fwd[1] + fnz * this.fwd[2]);
392
+
393
+ var viewFade = 1.0;
394
+ if (viewDot <= 0.0) {
395
+ viewFade = 0.0;
396
+ } else if (viewDot < 0.15) {
397
+ var u = viewDot / 0.15;
398
+ viewFade = u * u * (3.0 - 2.0 * u);
399
+ }
400
+
401
+ var showEyes = (pose.showEyes !== false) && (viewFade > 0.001);
402
+
403
+ if (showEyes) {
404
+ var zFace = (pose.zFace !== undefined ? pose.zFace : 0.508) * s * scZ;
405
+ var self = this;
406
+
407
+ var computeFeaturePoint = function (fx, fy) {
408
+ var wx = bx + (r00 * fx + r01 * fy + r02 * zFace);
409
+ var wy = by + (r10 * fx + r11 * fy + r12 * zFace);
410
+ var wz = bz + (r20 * fx + r21 * fy + r22 * zFace);
411
+ return self.projectWorldPoint(wx, wy, wz);
412
+ };
413
+
414
+ var getAngle = function (baseP, upP, slant) {
415
+ if (!baseP || !upP) return 0;
416
+ var adx = upP.x - baseP.x;
417
+ var ady = upP.y - baseP.y;
418
+ var baseAngle = Math.atan2(adx, -ady) * (180.0 / Math.PI);
419
+ return baseAngle - (slant * 180.0 / Math.PI);
420
+ };
421
+
422
+ var rawFeatures = null;
423
+ if (Array.isArray(pose.faceFeatures) && pose.faceFeatures.length > 0) {
424
+ rawFeatures = pose.faceFeatures;
425
+ } else if (Array.isArray(pose.features) && pose.features.length > 0) {
426
+ rawFeatures = pose.features;
427
+ } else {
428
+ var baseGap = (pose.eyeGap !== undefined ? pose.eyeGap : 0.27) * s * scX;
429
+ var eyeX = (pose.eyeShiftX || 0.0) * s * scX;
430
+ var baseEyeY = (0.01 - (pose.bulge || 0.0) * 0.02 + (pose.eyeShiftY || 0.0)) * s * scY;
431
+ var eyeYL = baseEyeY + (pose.eyeShiftYL || 0.0) * s * scY;
432
+ var eyeYR = baseEyeY + (pose.eyeShiftYR || 0.0) * s * scY;
433
+
434
+ var slantL = pose.eyeSlantL !== undefined ? pose.eyeSlantL : (pose.eyeSlant || 0.0);
435
+ var slantR = pose.eyeSlantR !== undefined ? pose.eyeSlantR : -(pose.eyeSlant || 0.0);
436
+
437
+ var sxL = pose.eyeScaleXL !== undefined ? pose.eyeScaleXL : (pose.eyeScaleX !== undefined ? pose.eyeScaleX : 1.0);
438
+ var sxR = pose.eyeScaleXR !== undefined ? pose.eyeScaleXR : (pose.eyeScaleX !== undefined ? pose.eyeScaleX : 1.0);
439
+ var syL = pose.eyeScaleYL !== undefined ? pose.eyeScaleYL : (pose.eyeScaleY !== undefined ? pose.eyeScaleY : 1.0);
440
+ var syR = pose.eyeScaleYR !== undefined ? pose.eyeScaleYR : (pose.eyeScaleY !== undefined ? pose.eyeScaleY : 1.0);
441
+
442
+ var eyeSxL = (1.0 + (pose.bulge || 0.0) * 0.45) * sxL;
443
+ var eyeSxR = (1.0 + (pose.bulge || 0.0) * 0.45) * sxR;
444
+ var eyeSyL = (1.0 + (pose.bulge || 0.0) * 0.65) * syL;
445
+ var eyeSyR = (1.0 + (pose.bulge || 0.0) * 0.65) * syR;
446
+
447
+ var squint = pose.squint || 0.0;
448
+ if (squint > 0.01) {
449
+ eyeSyL = eyeSyL * (1.0 - squint) + 0.02 * squint;
450
+ eyeSxL = eyeSxL * (1.0 - squint) + 0.85 * squint;
451
+ eyeSyR = eyeSyR * (1.0 - squint) + 0.02 * squint;
452
+ eyeSxR = eyeSxR * (1.0 - squint) + 0.85 * squint;
453
+ }
454
+
455
+ rawFeatures = [
456
+ {
457
+ id: 'eyeL',
458
+ type: 'pill',
459
+ x: -baseGap / 2 + eyeX,
460
+ y: eyeYL,
461
+ slant: slantL,
462
+ scaleX: eyeSxL,
463
+ scaleY: eyeSyL,
464
+ baseW: 0.125,
465
+ baseH: 0.210,
466
+ color: pose.eyeColor || '#ffffff'
467
+ },
468
+ {
469
+ id: 'eyeR',
470
+ type: 'pill',
471
+ x: baseGap / 2 + eyeX,
472
+ y: eyeYR,
473
+ slant: slantR,
474
+ scaleX: eyeSxR,
475
+ scaleY: eyeSyR,
476
+ baseW: 0.125,
477
+ baseH: 0.210,
478
+ color: pose.eyeColor || '#ffffff'
479
+ }
480
+ ];
481
+ }
482
+
483
+ var faceForeshorten = Math.max(0.85, Math.hypot(r00, r10));
484
+ var squashFade = 0.2 + 0.8 * viewFade;
485
+
486
+ for (var fi = 0; fi < rawFeatures.length; fi++) {
487
+ var feat = rawFeatures[fi];
488
+ var fx = (feat.x !== undefined ? feat.x : 0.0);
489
+ var fy = (feat.y !== undefined ? feat.y : 0.0);
490
+ var projP = computeFeaturePoint(fx, fy);
491
+ if (!projP) continue;
492
+
493
+ var projUp = computeFeaturePoint(fx, fy + 0.1 * s * scY);
494
+ var fslant = feat.slant || 0.0;
495
+ var angle = getAngle(projP, projUp, fslant);
496
+
497
+ var factor = projP.z > 0.01 ? (this.focalLength / projP.z) * this.halfSize : (this.focalLength / avgZ) * this.halfSize;
498
+ var baseW = feat.baseW !== undefined ? feat.baseW : 0.125;
499
+ var baseH = feat.baseH !== undefined ? feat.baseH : 0.210;
500
+ var scFeatX = feat.scaleX !== undefined ? feat.scaleX : 1.0;
501
+ var scFeatY = feat.scaleY !== undefined ? feat.scaleY : 1.0;
502
+
503
+ var ew = Math.max(0.2, baseW * s * scX * scFeatX * faceForeshorten * factor * squashFade);
504
+ var eh = Math.max(0.2, baseH * s * scY * scFeatY * factor);
505
+ var erx = feat.rx !== undefined ? feat.rx : Math.min(ew, eh) / 2.0;
506
+ var ery = feat.ry !== undefined ? feat.ry : Math.min(ew, eh) / 2.0;
507
+
508
+ var featOpacity = (feat.opacity !== undefined ? feat.opacity : 1.0) * viewFade;
509
+
510
+ eyes.push({
511
+ id: feat.id || ('feat-' + fi),
512
+ type: feat.type || 'pill',
513
+ cx: projP.x,
514
+ cy: projP.y,
515
+ z: projP.z,
516
+ w: ew,
517
+ h: eh,
518
+ rx: erx,
519
+ ry: ery,
520
+ angle: angle,
521
+ opacity: featOpacity,
522
+ color: feat.color || pose.eyeColor || '#ffffff'
523
+ });
524
+ }
525
+ }
526
+
527
+ return {
528
+ visible: true,
529
+ bodyPath: bodyPath,
530
+ eyes: eyes,
531
+ bodyColor: pose.bodyColor,
532
+ eyeColor: pose.eyeColor
533
+ };
534
+ };
535
+
536
+ /* =========================================================================
537
+ 3. Mascot Timelines & State Functions (#1 ~ #7)
538
+ ========================================================================= */
539
+
540
+ // --- #1 Sentry (哨兵) ---
541
+ var pBot1Start = { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: -0.02, roll: 0.01, eyeShiftX: 0.015, eyeShiftY: -0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeScaleXL: 1.0, eyeScaleXR: 1.0, eyeGap: 0.26, bulge: 0.0, jumpY: 0.0 };
542
+ var pBot1Left = { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.28, pitch: -0.03, roll: 0.02, eyeShiftX: -0.080, eyeShiftY: 0.01, eyeScaleX: 0.95, eyeScaleY: 1.0, eyeScaleXL: 0.94, eyeScaleXR: 1.0, eyeGap: 0.22, bulge: 0.0, jumpY: 0.0 };
543
+ var pBot1DeepLeft = { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.42, pitch: -0.05, roll: 0.04, eyeShiftX: -0.108, eyeShiftY: 0.03, eyeScaleX: 0.95, eyeScaleY: 1.0, eyeScaleXL: 0.92, eyeScaleXR: 1.0, eyeGap: 0.20, bulge: 0.0, jumpY: 0.0 };
544
+ var pBot1Right = { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.36, pitch: -0.04, roll: -0.03, eyeShiftX: 0.075, eyeShiftY: 0.02, eyeScaleX: 0.95, eyeScaleY: 1.0, eyeScaleXL: 1.0, eyeScaleXR: 0.94, eyeGap: 0.22, bulge: 0.0, jumpY: 0.0 };
545
+ var pBot1FarRight = { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.44, pitch: -0.05, roll: -0.04, eyeShiftX: 0.095, eyeShiftY: 0.025, eyeScaleX: 0.95, eyeScaleY: 1.0, eyeScaleXL: 1.0, eyeScaleXR: 0.92, eyeGap: 0.20, bulge: 0.0, jumpY: 0.0 };
546
+ var pBot1MidFocus = { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.00, pitch: -0.02, roll: 0.0, eyeShiftX: 0.00, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeScaleXL: 1.0, eyeScaleXR: 1.0, eyeGap: 0.26, bulge: 0.0, jumpY: 0.0 };
547
+ var pBot1Stretch = { scale: 1.0, scaleX: 0.97, scaleY: 1.04, yaw: 0.38, pitch: -0.06, roll: 0.0, eyeShiftX: 0.075, eyeShiftY: 0.04, eyeScaleX: 0.95, eyeScaleY: 1.0, eyeScaleXL: 1.0, eyeScaleXR: 0.94, eyeGap: 0.22, bulge: 0.0, jumpY: 0.012 };
548
+ var pBot1Tilt = { scale: 1.0, scaleX: 0.97, scaleY: 1.04, yaw: 0.40, pitch: -0.06, roll: 0.320, eyeShiftX: 0.085, eyeShiftY: 0.035, eyeScaleX: 0.95, eyeScaleY: 1.0, eyeScaleXL: 1.0, eyeScaleXR: 0.94, eyeGap: 0.21, bulge: 0.0, jumpY: 0.012 };
549
+
550
+ var bot1Timeline = new PoseTimeline([
551
+ { t: 0.00, label: 'sentry-scan-left-1', pose: pBot1Start },
552
+ { t: 1.65, label: 'sentry-scan-left-1', ease: 'linear', pose: pBot1Start },
553
+ { t: 2.15, label: 'sentry-sweep-right-1', ease: 'easeInOutQuad', pose: pBot1Right },
554
+ { t: 3.60, label: 'sentry-sweep-right-1', ease: 'linear', pose: pBot1Right },
555
+ { t: 3.88, label: 'sentry-scan-left-2', ease: 'easeInOutQuad', pose: pBot1Left },
556
+ { t: 4.65, label: 'sentry-scan-left-2', ease: 'linear', pose: pBot1Left },
557
+ { t: 5.30, label: 'sentry-sweep-right-2', ease: 'easeInOutQuad', pose: pBot1Right },
558
+ { t: 6.60, label: 'sentry-sweep-right-2', ease: 'linear', pose: pBot1Right },
559
+ { t: 7.10, label: 'sentry-sweep-far-right', ease: 'easeInOutQuad', pose: pBot1FarRight },
560
+ { t: 8.40, label: 'sentry-sweep-far-right', ease: 'linear', pose: pBot1FarRight },
561
+ { t: 8.75, label: 'sentry-deep-left-1', ease: 'easeInOutQuad', pose: pBot1DeepLeft },
562
+ { t: 9.35, label: 'sentry-deep-left-1', ease: 'linear', pose: pBot1DeepLeft },
563
+ { t: 9.90, label: 'sentry-sweep-right-3', ease: 'easeInOutQuad', pose: pBot1Right },
564
+ { t: 11.20, label: 'sentry-sweep-right-3', ease: 'linear', pose: pBot1Right },
565
+ { t: 11.55, label: 'sentry-center-focus', ease: 'easeInOutQuad', pose: pBot1MidFocus },
566
+ { t: 14.10, label: 'sentry-center-focus', ease: 'linear', pose: pBot1MidFocus },
567
+ { t: 14.45, label: 'sentry-deep-left-2', ease: 'easeInOutQuad', pose: pBot1DeepLeft },
568
+ { t: 15.15, label: 'sentry-deep-left-2', ease: 'linear', pose: pBot1DeepLeft },
569
+ { t: 15.65, label: 'sentry-sweep-right-4', ease: 'easeInOutQuad', pose: pBot1Right },
570
+ { t: 16.80, label: 'sentry-sweep-right-4', ease: 'linear', pose: pBot1Right },
571
+ { t: 17.05, label: 'sentry-stretch', ease: 'easeInOutQuad', pose: pBot1Stretch },
572
+ { t: 17.35, label: 'sentry-curious-tilt', ease: 'easeInOutQuad', pose: pBot1Tilt },
573
+ { t: 18.70, label: 'sentry-curious-tilt', ease: 'linear', pose: pBot1Tilt },
574
+ { t: 19.05, label: 'sentry-right-settle', ease: 'easeInOutQuad', pose: pBot1Right },
575
+ { t: 19.45, label: 'sentry-scan-left-3', ease: 'easeInOutQuad', pose: pBot1Left },
576
+ { t: 20.20, label: 'sentry-deep-left-3', ease: 'easeInOutQuad', pose: pBot1DeepLeft },
577
+ { t: 20.783, label: 'sentry-scan-left-1', ease: 'easeInOutQuad', pose: pBot1Start }
578
+ ]);
579
+
580
+ var bot1Blinks = new BlinkTrack([
581
+ { start: 1.85, duration: 0.18, maxSquint: 0.98 },
582
+ { start: 5.65, duration: 0.17, maxSquint: 0.98 },
583
+ { start: 8.80, duration: 0.18, maxSquint: 0.98 },
584
+ { start: 11.40, duration: 0.18, maxSquint: 0.98 },
585
+ { start: 11.68, duration: 0.19, maxSquint: 0.98 },
586
+ { start: 16.10, duration: 0.18, maxSquint: 0.98 },
587
+ { start: 18.96, duration: 0.18, maxSquint: 0.98 }
588
+ ]);
589
+
590
+ function getBot1State(time) {
591
+ var t = ((time % LOOP) + LOOP) % LOOP;
592
+ var res = bot1Timeline.evaluate(t);
593
+ var pose = res.pose;
594
+ var squint = bot1Blinks.evaluate(t);
595
+
596
+ var bot = {
597
+ visible: true,
598
+ scale: pose.scale,
599
+ scaleX: pose.scaleX,
600
+ scaleY: pose.scaleY,
601
+ x: 0.0,
602
+ y: pose.jumpY || 0.0,
603
+ yaw: pose.yaw,
604
+ pitch: pose.pitch,
605
+ roll: pose.roll,
606
+ eyeShiftX: pose.eyeShiftX,
607
+ eyeShiftY: pose.eyeShiftY,
608
+ eyeScaleX: pose.eyeScaleX,
609
+ eyeScaleY: pose.eyeScaleY,
610
+ eyeScaleXL: pose.eyeScaleXL !== undefined ? pose.eyeScaleXL : 1.0,
611
+ eyeScaleXR: pose.eyeScaleXR !== undefined ? pose.eyeScaleXR : 1.0,
612
+ eyeGap: pose.eyeGap !== undefined ? pose.eyeGap : 0.23,
613
+ bulge: pose.bulge || 0.0,
614
+ squint: squint,
615
+ bodyColor: 0x222126,
616
+ eyeColor: 0xffffff
617
+ };
618
+
619
+ return { botId: 1, type: 'bot1', label: res.label, bot: bot, dots: [] };
620
+ }
621
+
622
+ // --- #2 Attitude (傲娇小怪) ---
623
+ var bot2Apexes = [1.07, 4.78, 8.57, 12.20, 15.90, 19.62];
624
+ var BOT2_FLIP_HALF = 0.42;
625
+
626
+ function makeBot2RelaxedPose(cycle) {
627
+ return {
628
+ scale: 1.0, scaleX: 1.0, scaleY: 1.0,
629
+ yaw: 0.05, pitch: 0.02,
630
+ roll: 0.17 + cycle * 2 * Math.PI,
631
+ eyeShiftX: 0.04, eyeShiftY: 0.22,
632
+ eyeShiftYL: 0.0, eyeShiftYR: 0.0,
633
+ eyeScaleXL: 1.0, eyeScaleYL: 1.0,
634
+ eyeScaleXR: 1.0, eyeScaleYR: 1.0,
635
+ eyeSlantL: 0.0, eyeSlantR: 0.10,
636
+ eyeGap: 0.22, bulge: 0.0, jumpY: 0.0, showEyes: true
637
+ };
638
+ }
639
+
640
+ function makeBot2MidPose(cycle) {
641
+ return {
642
+ scale: 1.0, scaleX: 1.0, scaleY: 1.0,
643
+ yaw: 0.85, pitch: 0.35,
644
+ roll: 0.17 + (cycle * 2 + 1) * Math.PI,
645
+ eyeShiftX: 0.04, eyeShiftY: 0.22,
646
+ eyeShiftYL: 0.0, eyeShiftYR: 0.0,
647
+ eyeScaleXL: 0.85, eyeScaleYL: 0.85,
648
+ eyeScaleXR: 0.85, eyeScaleYR: 0.85,
649
+ eyeSlantL: 0.0, eyeSlantR: 0.0,
650
+ eyeGap: 0.22, bulge: 0.0, jumpY: 0.075, showEyes: false
651
+ };
652
+ }
653
+
654
+ function makeBot2SquintPose(cycle) {
655
+ return {
656
+ scale: 1.0, scaleX: 1.0, scaleY: 1.0,
657
+ yaw: 0.05, pitch: 0.02,
658
+ roll: 0.17 + cycle * 2 * Math.PI,
659
+ eyeShiftX: 0.04, eyeShiftY: 0.22,
660
+ eyeShiftYL: -0.015, eyeShiftYR: +0.020,
661
+ eyeScaleXL: 0.60, eyeScaleXR: 0.60,
662
+ eyeScaleYL: 0.85, eyeScaleYR: 0.85,
663
+ eyeSlantL: 0.56, eyeSlantR: -0.51,
664
+ eyeGap: 0.22, bulge: 0.0, jumpY: 0.0, showEyes: true
665
+ };
666
+ }
667
+
668
+ var bot2Keyframes = [
669
+ { t: 0.00, label: 'standby-relaxed-0', pose: makeBot2RelaxedPose(0) }
670
+ ];
671
+
672
+ for (var bi = 0; bi < bot2Apexes.length; bi++) {
673
+ var apex = bot2Apexes[bi];
674
+ var tTakeoff = apex - BOT2_FLIP_HALF;
675
+ var tMid = apex;
676
+ var tLand = apex + BOT2_FLIP_HALF;
677
+ var tSquintSnap = tLand + 0.08;
678
+
679
+ bot2Keyframes.push({ t: tTakeoff, label: 'takeoff-' + bi, ease: 'linear', pose: makeBot2RelaxedPose(bi) });
680
+ bot2Keyframes.push({ t: tMid, label: 'spin-flip-mid-' + (bi + 1), ease: 'easeInOutQuad', pose: makeBot2MidPose(bi) });
681
+ bot2Keyframes.push({ t: tLand, label: 'spin-flip-land-' + (bi + 1), ease: 'easeInOutQuad', pose: makeBot2SquintPose(bi + 1) });
682
+ bot2Keyframes.push({ t: tSquintSnap, label: 'attitude-squint-' + (bi + 1), ease: 'linear', pose: makeBot2SquintPose(bi + 1) });
683
+
684
+ if (bi < bot2Apexes.length - 1) {
685
+ var nextApex = bot2Apexes[bi + 1];
686
+ var nextTakeoff = nextApex - BOT2_FLIP_HALF;
687
+ var tSquintHoldEnd = nextTakeoff - 0.65;
688
+ var tRelaxEnd = nextTakeoff - 0.35;
689
+
690
+ bot2Keyframes.push({ t: tSquintHoldEnd, label: 'attitude-hold-' + (bi + 1), ease: 'linear', pose: makeBot2SquintPose(bi + 1) });
691
+ bot2Keyframes.push({ t: tRelaxEnd, label: 'attitude-relax-' + (bi + 1), ease: 'easeInOutQuad', pose: makeBot2RelaxedPose(bi + 1) });
692
+ } else {
693
+ bot2Keyframes.push({ t: 20.45, label: 'attitude-hold-6', ease: 'linear', pose: makeBot2SquintPose(6) });
694
+ bot2Keyframes.push({ t: 20.783, label: 'attitude-relax-end', ease: 'easeInOutQuad', pose: makeBot2RelaxedPose(6) });
695
+ }
696
+ }
697
+
698
+ var bot2Timeline = new PoseTimeline(bot2Keyframes);
699
+ var bot2Blinks = new BlinkTrack([]);
700
+
701
+ function getBot2State(time) {
702
+ var t = ((time % LOOP) + LOOP) % LOOP;
703
+ var res = bot2Timeline.evaluate(t);
704
+ var pose = res.pose;
705
+ var squint = bot2Blinks.evaluate(t);
706
+
707
+ var squintWeight = Math.max(0.0, Math.min(1.0, (pose.eyeSlantL || 0.0) / 0.56));
708
+ var omegaShake = 2.2 * Math.PI * 2.0;
709
+ var headShakeYaw = Math.sin(t * omegaShake) * 0.038 * squintWeight;
710
+ var headShakeRoll = Math.cos(t * omegaShake) * 0.024 * squintWeight;
711
+ var headShakeEyeX = Math.sin(t * omegaShake) * 0.010 * squintWeight;
712
+ var headShakeEyeY = Math.abs(Math.sin(t * omegaShake)) * 0.005 * squintWeight;
713
+
714
+ var bot = {
715
+ visible: true,
716
+ scale: pose.scale,
717
+ scaleX: pose.scaleX,
718
+ scaleY: pose.scaleY,
719
+ x: 0.0,
720
+ y: pose.jumpY || 0.0,
721
+ yaw: (pose.yaw || 0.0) + headShakeYaw,
722
+ pitch: pose.pitch,
723
+ roll: (pose.roll || 0.0) + headShakeRoll,
724
+ eyeShiftX: (pose.eyeShiftX || 0.0) + headShakeEyeX,
725
+ eyeShiftY: (pose.eyeShiftY || 0.0) + headShakeEyeY,
726
+ eyeShiftYL: pose.eyeShiftYL,
727
+ eyeShiftYR: pose.eyeShiftYR,
728
+ eyeScaleX: pose.eyeScaleX,
729
+ eyeScaleY: pose.eyeScaleY,
730
+ eyeScaleXL: pose.eyeScaleXL,
731
+ eyeScaleXR: pose.eyeScaleXR,
732
+ eyeScaleYL: pose.eyeScaleYL,
733
+ eyeScaleYR: pose.eyeScaleYR,
734
+ eyeSlantL: pose.eyeSlantL,
735
+ eyeSlantR: pose.eyeSlantR,
736
+ eyeGap: pose.eyeGap !== undefined ? pose.eyeGap : 0.22,
737
+ bulge: pose.bulge || 0.0,
738
+ squint: squint,
739
+ showEyes: pose.showEyes,
740
+ bodyColor: 0x222126,
741
+ eyeColor: 0xffffff
742
+ };
743
+
744
+ return { botId: 2, type: 'bot2', label: res.label, bot: bot, dots: [] };
745
+ }
746
+
747
+ // --- #3 Chameleon (动感变色龙) ---
748
+ var BOT3_PALETTE_12 = [
749
+ [ 58, 53, 206], // 0.0s 钴蓝
750
+ [ 60, 84, 208], // 0.5s 浅钴蓝
751
+ [201, 34, 153], // 1.0s 洋红
752
+ [201, 222, 65], // 1.5s 柠黄
753
+ [ 68, 229, 198], // 2.0s 青绿
754
+ [ 66, 162, 209], // 2.5s 天蓝
755
+ [ 69, 238, 80], // 3.0s 亮绿
756
+ [200, 41, 71], // 3.5s 绯红
757
+ [125, 38, 208], // 4.0s 紫色
758
+ [100, 42, 209], // 4.5s 蓝紫
759
+ [154, 36, 211], // 5.0s 亮紫
760
+ [199, 35, 104] // 5.5s 玫红
761
+ ];
762
+
763
+ function getBot3ColorHex(t) {
764
+ var cycle = ((t % 6.00) + 6.00) % 6.00 / 6.00;
765
+ var n = BOT3_PALETTE_12.length;
766
+ var p = cycle * n;
767
+ var i0 = Math.floor(p) % n;
768
+ var i1 = (i0 + 1) % n;
769
+ var f = p - Math.floor(p);
770
+ var s = f * f * (3 - 2 * f);
771
+ var r = Math.round(BOT3_PALETTE_12[i0][0] * (1 - s) + BOT3_PALETTE_12[i1][0] * s);
772
+ var g = Math.round(BOT3_PALETTE_12[i0][1] * (1 - s) + BOT3_PALETTE_12[i1][1] * s);
773
+ var b = Math.round(BOT3_PALETTE_12[i0][2] * (1 - s) + BOT3_PALETTE_12[i1][2] * s);
774
+ return (r << 16) | (g << 8) | b;
775
+ }
776
+
777
+ var bot3Timeline = new PoseTimeline([
778
+ { t: 0.00, label: 'chameleon-normal', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
779
+ { t: 1.20, label: 'chameleon-normal', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
780
+ { t: 1.50, label: 'chameleon-slit-1', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
781
+ { t: 1.70, label: 'chameleon-slit-1', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
782
+ { t: 1.95, label: 'chameleon-dot-1', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.40, eyeScaleY: 0.40, eyeScaleXL: 0.40, eyeScaleXR: 0.40, eyeScaleYL: 0.40, eyeScaleYR: 0.40, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
783
+ { t: 2.15, label: 'chameleon-dot-1', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.40, eyeScaleY: 0.40, eyeScaleXL: 0.40, eyeScaleXR: 0.40, eyeScaleYL: 0.40, eyeScaleYR: 0.40, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
784
+ { t: 2.45, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
785
+ { t: 2.75, label: 'chameleon-slit-2', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
786
+ { t: 3.15, label: 'chameleon-slit-2', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
787
+ { t: 3.50, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
788
+ { t: 4.80, label: 'chameleon-slit-3', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
789
+ { t: 5.30, label: 'chameleon-slit-3', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
790
+ { t: 5.70, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
791
+ { t: 8.60, label: 'chameleon-normal', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
792
+ { t: 8.90, label: 'chameleon-corner-glance-1', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.15, pitch: -0.04, roll: -0.10, eyeShiftX: -0.16, eyeShiftY: 0.11, eyeGap: 0.36, eyeScaleX: 0.52, eyeScaleY: 0.52, eyeScaleXL: 0.50, eyeScaleXR: 0.55, eyeScaleYL: 0.50, eyeScaleYR: 0.55, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
793
+ { t: 9.20, label: 'chameleon-corner-glance-1', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.15, pitch: -0.04, roll: -0.10, eyeShiftX: -0.16, eyeShiftY: 0.11, eyeGap: 0.36, eyeScaleX: 0.52, eyeScaleY: 0.52, eyeScaleXL: 0.50, eyeScaleXR: 0.55, eyeScaleYL: 0.50, eyeScaleYR: 0.55, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
794
+ { t: 9.50, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
795
+ { t: 11.20, label: 'chameleon-normal', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
796
+ { t: 11.50, label: 'chameleon-asym', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.50, eyeScaleY: 0.62, eyeScaleXL: 0.40, eyeScaleXR: 0.60, eyeScaleYL: 0.40, eyeScaleYR: 0.85, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
797
+ { t: 11.80, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
798
+ { t: 14.60, label: 'chameleon-normal', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
799
+ { t: 14.85, label: 'chameleon-corner-glance-2', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.15, pitch: -0.04, roll: -0.10, eyeShiftX: -0.16, eyeShiftY: 0.11, eyeGap: 0.36, eyeScaleX: 0.52, eyeScaleY: 0.52, eyeScaleXL: 0.50, eyeScaleXR: 0.55, eyeScaleYL: 0.50, eyeScaleYR: 0.55, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
800
+ { t: 15.05, label: 'chameleon-corner-glance-2', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.15, pitch: -0.04, roll: -0.10, eyeShiftX: -0.16, eyeShiftY: 0.11, eyeGap: 0.36, eyeScaleX: 0.52, eyeScaleY: 0.52, eyeScaleXL: 0.50, eyeScaleXR: 0.55, eyeScaleYL: 0.50, eyeScaleYR: 0.55, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
801
+ { t: 15.18, label: 'chameleon-dazed', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.02, pitch: 0.01, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.75, eyeScaleY: 0.45, eyeScaleXL: 0.75, eyeScaleXR: 0.75, eyeScaleYL: 0.45, eyeScaleYR: 0.45, eyeSlantL: 0.10, eyeSlantR: -0.10, bulge: 0.0, jumpY: 0.0 } },
802
+ { t: 16.14, label: 'chameleon-dazed', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.02, pitch: 0.01, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.75, eyeScaleY: 0.45, eyeScaleXL: 0.75, eyeScaleXR: 0.75, eyeScaleYL: 0.45, eyeScaleYR: 0.45, eyeSlantL: 0.10, eyeSlantR: -0.10, bulge: 0.0, jumpY: 0.0 } },
803
+ { t: 16.40, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
804
+ { t: 17.00, label: 'chameleon-slit-4', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
805
+ { t: 17.60, label: 'chameleon-slit-4', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.52, eyeScaleY: 0.82, eyeScaleXL: 0.52, eyeScaleXR: 0.52, eyeScaleYL: 0.82, eyeScaleYR: 0.82, eyeSlantL: 0.58, eyeSlantR: -0.58, bulge: 0.0, jumpY: 0.0 } },
806
+ { t: 18.00, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
807
+ { t: 19.80, label: 'chameleon-normal', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
808
+ { t: 20.00, label: 'chameleon-corner-glance-3', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.15, pitch: -0.04, roll: -0.10, eyeShiftX: -0.16, eyeShiftY: 0.11, eyeGap: 0.36, eyeScaleX: 0.52, eyeScaleY: 0.52, eyeScaleXL: 0.50, eyeScaleXR: 0.55, eyeScaleYL: 0.50, eyeScaleYR: 0.55, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
809
+ { t: 20.25, label: 'chameleon-corner-glance-3', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.15, pitch: -0.04, roll: -0.10, eyeShiftX: -0.16, eyeShiftY: 0.11, eyeGap: 0.36, eyeScaleX: 0.52, eyeScaleY: 0.52, eyeScaleXL: 0.50, eyeScaleXR: 0.55, eyeScaleYL: 0.50, eyeScaleYR: 0.55, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
810
+ { t: 20.50, label: 'chameleon-normal', ease: 'easeInOutQuad', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } },
811
+ { t: 20.783, label: 'chameleon-normal', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.05, pitch: 0.02, roll: -0.10, eyeShiftX: 0.02, eyeShiftY: 0.09, eyeGap: 0.27, eyeScaleX: 0.90, eyeScaleY: 1.05, eyeScaleXL: 0.90, eyeScaleXR: 0.90, eyeScaleYL: 1.05, eyeScaleYR: 1.05, eyeSlantL: 0.0, eyeSlantR: 0.0, bulge: 0.0, jumpY: 0.0 } }
812
+ ]);
813
+
814
+ var bot3Blinks = new BlinkTrack([
815
+ { start: 1.85, duration: 0.18, maxSquint: 0.95 },
816
+ { start: 5.65, duration: 0.17, maxSquint: 0.95 },
817
+ { start: 8.80, duration: 0.18, maxSquint: 0.95 },
818
+ { start: 11.40, duration: 0.18, maxSquint: 0.95 },
819
+ { start: 11.70, duration: 0.18, maxSquint: 0.95 },
820
+ { start: 16.12, duration: 0.18, maxSquint: 0.95 },
821
+ { start: 18.96, duration: 0.18, maxSquint: 0.95 }
822
+ ]);
823
+
824
+ function getBot3State(time) {
825
+ var t = ((time % LOOP) + LOOP) % LOOP;
826
+ var colorHex = getBot3ColorHex(t);
827
+ var res = bot3Timeline.evaluate(t);
828
+ var pose = res.pose;
829
+ var squint = bot3Blinks.evaluate(t);
830
+
831
+ var N_DANCE_CYCLES = 21;
832
+ var omegaSway = (2.0 * Math.PI * N_DANCE_CYCLES) / LOOP;
833
+ var omegaBounce = 4.0 * omegaSway;
834
+
835
+ var bouncePhase = (t * omegaBounce) % (2.0 * Math.PI);
836
+ var bounceShape = Math.pow(Math.sin(bouncePhase / 2.0), 2.0);
837
+ var bounceY = (bounceShape - 0.48) * 0.052;
838
+
839
+ var squashNorm = (bounceShape - 0.48) * 2.0;
840
+ var danceScaleX = (pose.scaleX || 1.0) * (1.0 + squashNorm * 0.068);
841
+ var danceScaleY = (pose.scaleY || 1.0) * (1.0 - squashNorm * 0.068);
842
+
843
+ var swayX = Math.sin(t * omegaSway) * 0.15;
844
+ var swayRoll = -Math.sin(t * omegaSway) * 0.20 + Math.cos(t * omegaBounce) * 0.035;
845
+
846
+ var bot = {
847
+ visible: true,
848
+ scale: pose.scale || 1.0,
849
+ scaleX: danceScaleX,
850
+ scaleY: danceScaleY,
851
+ x: (pose.x || 0.0) + swayX,
852
+ y: (pose.jumpY || 0.0) + bounceY,
853
+ jumpY: (pose.jumpY || 0.0) + bounceY,
854
+ yaw: pose.yaw || 0.0,
855
+ pitch: pose.pitch || 0.0,
856
+ roll: (pose.roll || 0.0) + swayRoll,
857
+ eyeShiftX: pose.eyeShiftX !== undefined ? pose.eyeShiftX : 0.02,
858
+ eyeShiftY: pose.eyeShiftY !== undefined ? pose.eyeShiftY : (0.09 - bounceY * 0.25),
859
+ eyeScaleX: pose.eyeScaleX,
860
+ eyeScaleY: pose.eyeScaleY,
861
+ eyeScaleXL: pose.eyeScaleXL !== undefined ? pose.eyeScaleXL : (pose.eyeScaleX !== undefined ? pose.eyeScaleX : 0.90),
862
+ eyeScaleXR: pose.eyeScaleXR !== undefined ? pose.eyeScaleXR : (pose.eyeScaleX !== undefined ? pose.eyeScaleX : 0.90),
863
+ eyeScaleYL: pose.eyeScaleYL !== undefined ? pose.eyeScaleYL : (pose.eyeScaleY !== undefined ? pose.eyeScaleY : 1.05),
864
+ eyeScaleYR: pose.eyeScaleYR !== undefined ? pose.eyeScaleYR : (pose.eyeScaleY !== undefined ? pose.eyeScaleY : 1.05),
865
+ eyeSlantL: pose.eyeSlantL !== undefined ? pose.eyeSlantL : (pose.eyeSlant || 0.0),
866
+ eyeSlantR: pose.eyeSlantR !== undefined ? pose.eyeSlantR : -(pose.eyeSlant || 0.0),
867
+ eyeGap: pose.eyeGap !== undefined ? pose.eyeGap : 0.27,
868
+ bulge: pose.bulge || 0.0,
869
+ squint: squint,
870
+ bodyColor: colorHex,
871
+ eyeColor: 0x222126
872
+ };
873
+
874
+ return { botId: 3, type: 'bot3', label: res.label, bot: bot, dots: [] };
875
+ }
876
+
877
+ // --- #4 Observer (屏读观察者) ---
878
+ var BOT4_T_ROW = LOOP / 16.0;
879
+ var pBot4Pose = function (yaw, eyeShiftX) {
880
+ return {
881
+ scale: 1.0, scaleX: 1.0, scaleY: 1.0,
882
+ yaw: yaw, pitch: 0.0, roll: 0.0,
883
+ eyeShiftX: eyeShiftX, eyeShiftY: 0.0,
884
+ bulge: 0.0, jumpY: 0.0
885
+ };
886
+ };
887
+
888
+ var bot4Keyframes = [
889
+ { t: 0.00, label: 'raster-0-right', pose: pBot4Pose(0.26, 0.042) }
890
+ ];
891
+
892
+ for (var k = 0; k < 16; k++) {
893
+ var tBase = k * BOT4_T_ROW;
894
+ var tSnapStart = tBase + 0.12;
895
+ var tLeftLand = tBase + 0.24;
896
+ var tLeftEnd = tBase + 0.52;
897
+ var tCenterLand = tBase + 0.60;
898
+ var tCenterEnd = tBase + 0.88;
899
+ var tRightLand = tBase + 0.96;
900
+
901
+ bot4Keyframes.push({ t: tSnapStart, label: 'raster-' + k + '-hold-right', ease: 'linear', pose: pBot4Pose(0.26, 0.042) });
902
+ bot4Keyframes.push({ t: tLeftLand, label: 'raster-' + k + '-snap-left', ease: 'easeInOutQuad', pose: pBot4Pose(-0.26, -0.042) });
903
+ bot4Keyframes.push({ t: tLeftEnd, label: 'raster-' + k + '-hold-left', ease: 'linear', pose: pBot4Pose(-0.26, -0.042) });
904
+ bot4Keyframes.push({ t: tCenterLand, label: 'raster-' + k + '-step-center', ease: 'easeInOutQuad', pose: pBot4Pose(0.00, 0.000) });
905
+ bot4Keyframes.push({ t: tCenterEnd, label: 'raster-' + k + '-hold-center', ease: 'linear', pose: pBot4Pose(0.00, 0.000) });
906
+ bot4Keyframes.push({ t: tRightLand, label: 'raster-' + k + '-step-right', ease: 'easeInOutQuad', pose: pBot4Pose(0.26, 0.042) });
907
+
908
+ if (k < 15) {
909
+ bot4Keyframes.push({ t: (k + 1) * BOT4_T_ROW, label: 'raster-' + (k + 1) + '-init', ease: 'linear', pose: pBot4Pose(0.26, 0.042) });
910
+ }
911
+ }
912
+ bot4Keyframes.push({ t: LOOP, label: 'raster-end', ease: 'linear', pose: pBot4Pose(0.26, 0.042) });
913
+
914
+ var bot4Timeline = new PoseTimeline(bot4Keyframes);
915
+
916
+ function getBot4State(time) {
917
+ var t = ((time % LOOP) + LOOP) % LOOP;
918
+ var res = bot4Timeline.evaluate(t);
919
+ var pose = res.pose;
920
+
921
+ var bot = {
922
+ visible: true,
923
+ scale: pose.scale,
924
+ scaleX: pose.scaleX,
925
+ scaleY: pose.scaleY,
926
+ x: 0.0,
927
+ y: 0.0,
928
+ yaw: pose.yaw,
929
+ pitch: pose.pitch,
930
+ roll: pose.roll,
931
+ eyeShiftX: pose.eyeShiftX,
932
+ eyeShiftY: pose.eyeShiftY,
933
+ eyeScaleX: 0.96,
934
+ eyeScaleY: 0.96,
935
+ eyeGap: 0.22,
936
+ bulge: 0.0,
937
+ squint: 0.0,
938
+ bodyColor: 0x222126,
939
+ eyeColor: 0xffffff
940
+ };
941
+
942
+ return { botId: 4, type: 'bot4', label: res.label, bot: bot, dots: [] };
943
+ }
944
+
945
+ // --- #5 Cube & Grid (方块与点阵) ---
946
+ var bot5Timeline = new PoseTimeline([
947
+ { t: 0.00, label: 'tucked', pose: { scaleX: 1.0, scaleY: 1.0, visible: false, scale: 0.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
948
+ { t: 2.22, label: 'tucked', pose: { scaleX: 1.0, scaleY: 1.0, visible: false, scale: 0.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
949
+ { t: 2.45, label: 'bouncy-pop-spin', ease: 'easeInOutQuad', pose: { scaleX: 1.0, scaleY: 1.0, visible: true, scale: 1.08, yaw: -Math.PI * 0.55, pitch: -0.02, roll: 0.04, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.06 } },
950
+ { t: 2.68, label: 'bouncy-pop-spin', ease: 'easeInOutQuad', pose: { scaleX: 1.0, scaleY: 1.0, visible: true, scale: 1.03, yaw: -Math.PI * 1.03, pitch: -0.04, roll: 0.02, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.02 } },
951
+ { t: 2.92, label: 'spin-reappear', ease: 'easeInOutQuad', pose: { scaleX: 1.0, scaleY: 1.0, visible: true, scale: 1.00, yaw: -Math.PI * 1.58, pitch: -0.05, roll: -0.03, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
952
+ { t: 3.06, label: 'land-settle', ease: 'easeOutCubic', pose: { scaleX: 1.0, scaleY: 1.0, visible: true, scale: 1.00, yaw: -Math.PI * 2.0 + 0.31, pitch: -0.05, roll: -0.05, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
953
+ { t: 3.28, label: 'eye-bulge', ease: 'easeInOutQuad', pose: { scaleX: 1.0, scaleY: 1.0, visible: true, scale: 1.00, yaw: -Math.PI * 2.0 + 0.31, pitch: -0.05, roll: -0.05, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 1.0, squint: 0.0, jumpY: 0.0 } },
954
+ { t: 3.48, label: 'look-right-settle', ease: 'easeInOutQuad', pose: { scaleX: 1.0, scaleY: 1.0, visible: true, scale: 1.0, yaw: -Math.PI * 2.0 + 0.31, pitch: -0.05, roll: -0.05, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
955
+ { t: 3.64, label: 'look-right-settle', ease: 'linear', pose: { scale: 1.0, scaleX: 1.0, scaleY: 1.0, visible: true, scale: 1.0, yaw: -Math.PI * 2.0 + 0.31, pitch: -0.05, roll: -0.05, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
956
+ { t: 3.94, label: 'turn-to-lookup', ease: 'easeInOutQuad', pose: { visible: true, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 - 0.78, pitch: -0.21, roll: 0.0, eyeShiftX: -0.10, eyeShiftY: 0.035, eyeScaleX: 0.95, eyeScaleY: 0.88, eyeGap: 0.22, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
957
+ { t: 4.28, label: 'lookup-still-blink-2', ease: 'linear', pose: { visible: true, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 - 0.78, pitch: -0.21, roll: 0.0, eyeShiftX: -0.10, eyeShiftY: 0.035, eyeScaleX: 0.95, eyeScaleY: 0.88, eyeGap: 0.22, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
958
+ { t: 4.75, label: 'transition-to-right', ease: 'easeInOutQuad', pose: { visible: true, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 + 0.31, pitch: -0.05, roll: -0.05, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
959
+ { t: 6.12, label: 'look-right-curious', ease: 'linear', pose: { visible: true, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 + 0.31, pitch: -0.05, roll: -0.05, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
960
+ { t: 6.24, label: 'anticipation-squash', ease: 'easeInOutQuad', pose: { visible: true, scale: 1.0, scaleX: 1.16, scaleY: 0.85, yaw: -Math.PI * 2.0 + 0.28, pitch: -0.02, roll: -0.02, eyeShiftX: -0.05, eyeShiftY: 0.025, eyeScaleX: 1.10, eyeScaleY: 0.88, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: -0.035 } },
961
+ { t: 6.36, label: 'anticipation-rebound', ease: 'easeInOutQuad', pose: { visible: true, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 + 0.35, pitch: -0.04, roll: -0.04, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.02 } },
962
+ { t: 6.52, label: 'exit-spin-right', ease: 'easeInOutQuad', pose: { visible: true, scale: 0.94, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 + 1.45, pitch: -0.03, roll: -0.02, eyeShiftX: -0.05, eyeShiftY: 0.035, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
963
+ { t: 6.72, label: 'exit-spin-right', ease: 'easeInOutQuad', pose: { visible: true, scale: 0.45, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 + 2.55, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
964
+ { t: 6.88, label: 'exit-spin-right', ease: 'easeInOutQuad', pose: { visible: false, scale: 0.0, scaleX: 1.0, scaleY: 1.0, yaw: -Math.PI * 2.0 + 3.40, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } },
965
+ { t: 20.783, label: 'idle', ease: 'linear', pose: { scaleX: 1.0, scaleY: 1.0, visible: false, scale: 0.0, yaw: -Math.PI * 2.0 + 3.40, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, eyeGap: 0.27, bulge: 0.0, squint: 0.0, jumpY: 0.0 } }
966
+ ]);
967
+
968
+ var bot5Blinks = new BlinkTrack([
969
+ { start: 3.73, duration: 0.12, maxSquint: 0.95 },
970
+ { start: 4.06, duration: 0.12, maxSquint: 0.95 }
971
+ ]);
972
+ var bot5Dots = new DotGridRig(38.0, 10.1);
973
+
974
+ function getBot5State(time) {
975
+ var t = ((time % LOOP) + LOOP) % LOOP;
976
+ var res = bot5Timeline.evaluate(t);
977
+ var pose = res.pose;
978
+ pose.squint = bot5Blinks.evaluate(t);
979
+ pose.y = (pose.jumpY || 0.0);
980
+
981
+ if (t >= 4.75 && t < 6.12) {
982
+ pose.scale += Math.sin((t - 4.75) * 2.5) * 0.012;
983
+ }
984
+ var dots = bot5Dots.evaluate(t, pose.scale, pose.visible);
985
+
986
+ return {
987
+ botId: 5,
988
+ type: 'bot5',
989
+ label: res.label,
990
+ dots: dots,
991
+ bot: {
992
+ visible: pose.visible,
993
+ scale: pose.scale,
994
+ scaleX: pose.scaleX,
995
+ scaleY: pose.scaleY,
996
+ x: pose.x || 0.0,
997
+ y: pose.y,
998
+ yaw: pose.yaw,
999
+ pitch: pose.pitch,
1000
+ roll: pose.roll,
1001
+ eyeShiftX: pose.eyeShiftX,
1002
+ eyeShiftY: pose.eyeShiftY,
1003
+ eyeScaleX: pose.eyeScaleX,
1004
+ eyeScaleY: pose.eyeScaleY,
1005
+ eyeGap: pose.eyeGap,
1006
+ bulge: pose.bulge,
1007
+ squint: pose.squint,
1008
+ bodyColor: 0x222126,
1009
+ eyeColor: 0xffffff
1010
+ }
1011
+ };
1012
+ }
1013
+
1014
+ // --- #6 Ghost Trail (幽灵分身) ---
1015
+ var bot6Timeline = new PoseTimeline([
1016
+ { t: 0.00, label: 'ghost-blink-1', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1017
+ { t: 0.40, label: 'giant-bulge-1-start', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.05, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.04, bulge: 0.6, jumpY: 0.0 } },
1018
+ { t: 0.65, label: 'giant-bulge-1-peak', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.05, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.08, bulge: 1.25, jumpY: 0.0 } },
1019
+ { t: 0.95, label: 'single-solid-1', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1020
+ { t: 2.40, label: 'single-solid-1', ease: 'linear', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1021
+ { t: 3.10, label: 'lookup-left-1', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.42, pitch: -0.04, roll: 0.02, eyeShiftX: -0.06, eyeShiftY: 0.02, bulge: 0.0, jumpY: 0.0 } },
1022
+ { t: 3.35, label: 'squash-prep-1', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 0.92, scaleX: 1.08, scaleY: 0.92, yaw: -0.40, pitch: -0.03, roll: 0.01, eyeShiftX: -0.055, eyeShiftY: -0.02, bulge: 0.0, jumpY: -0.01 } },
1023
+ { t: 3.65, label: 'purple-solo-split-1', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 0.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: -0.38, pitch: -0.02, roll: -0.03, eyeShiftX: -0.05, eyeShiftY: 0.01, bulge: 0.0, jumpY: 0.0 } },
1024
+ { t: 3.95, label: 'look-right-prep', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 0.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.38, pitch: -0.02, roll: 0.02, eyeShiftX: 0.05, eyeShiftY: 0.01, bulge: 0.0, jumpY: 0.0 } },
1025
+ { t: 4.20, label: 'cyan-solo-split-1', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 1.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.34, pitch: 0.0, roll: 0.0, eyeShiftX: 0.04, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1026
+ { t: 4.50, label: 'triple-dance-1', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 1.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1027
+ { t: 7.40, label: 'triple-dance-1', ease: 'linear', pose: { splitL: 1.0, splitR: 1.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1028
+ { t: 7.80, label: 'ghost-merge-1', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1029
+ { t: 8.50, label: 'single-solid-1-end', ease: 'linear', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1030
+ { t: 8.50, label: 'ghost-blink-2', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1031
+ { t: 8.90, label: 'giant-bulge-2-start', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.05, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.04, bulge: 0.6, jumpY: 0.0 } },
1032
+ { t: 9.15, label: 'giant-bulge-2-peak', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.05, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.08, bulge: 1.25, jumpY: 0.0 } },
1033
+ { t: 9.45, label: 'single-solid-2', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1034
+ { t: 10.90, label: 'single-solid-2', ease: 'linear', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1035
+ { t: 11.60, label: 'lookup-left-2', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.42, pitch: -0.04, roll: 0.02, eyeShiftX: -0.06, eyeShiftY: 0.02, bulge: 0.0, jumpY: 0.0 } },
1036
+ { t: 11.85, label: 'squash-prep-2', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 0.92, scaleX: 1.08, scaleY: 0.92, yaw: -0.40, pitch: -0.03, roll: 0.01, eyeShiftX: -0.055, eyeShiftY: -0.02, bulge: 0.0, jumpY: -0.01 } },
1037
+ { t: 12.15, label: 'purple-solo-split-2', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 0.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: -0.38, pitch: -0.02, roll: -0.03, eyeShiftX: -0.05, eyeShiftY: 0.01, bulge: 0.0, jumpY: 0.0 } },
1038
+ { t: 12.45, label: 'look-right-prep-2', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 0.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.38, pitch: -0.02, roll: 0.02, eyeShiftX: 0.05, eyeShiftY: 0.01, bulge: 0.0, jumpY: 0.0 } },
1039
+ { t: 12.70, label: 'cyan-solo-split-2', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 1.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.34, pitch: 0.0, roll: 0.0, eyeShiftX: 0.04, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1040
+ { t: 13.00, label: 'triple-dance-2', ease: 'easeInOutQuad', pose: { splitL: 1.0, splitR: 1.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1041
+ { t: 15.90, label: 'triple-dance-2', ease: 'linear', pose: { splitL: 1.0, splitR: 1.0, scale: 0.46, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1042
+ { t: 16.30, label: 'ghost-merge-2', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1043
+ { t: 17.00, label: 'single-solid-2-end', ease: 'linear', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1044
+ { t: 17.00, label: 'ghost-blink-3', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1045
+ { t: 17.40, label: 'giant-bulge-3-start', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.05, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.04, bulge: 0.6, jumpY: 0.0 } },
1046
+ { t: 17.65, label: 'giant-bulge-3-peak', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.05, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.08, bulge: 1.25, jumpY: 0.0 } },
1047
+ { t: 17.95, label: 'single-solid-3', ease: 'easeInOutQuad', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } },
1048
+ { t: 20.783, label: 'single-solid-3', ease: 'linear', pose: { splitL: 0.0, splitR: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, bulge: 0.0, jumpY: 0.0 } }
1049
+ ]);
1050
+
1051
+ var bot6Blinks = new BlinkTrack([
1052
+ { start: 0.00, duration: 0.25, maxSquint: 1.0 },
1053
+ { start: 1.05, duration: 0.12, maxSquint: 0.95 },
1054
+ { start: 2.70, duration: 0.12, maxSquint: 0.95 },
1055
+ { start: 3.20, duration: 0.14, maxSquint: 0.95 },
1056
+ { start: 8.50, duration: 0.25, maxSquint: 1.0 },
1057
+ { start: 9.55, duration: 0.12, maxSquint: 0.95 },
1058
+ { start: 11.20, duration: 0.12, maxSquint: 0.95 },
1059
+ { start: 11.70, duration: 0.14, maxSquint: 0.95 },
1060
+ { start: 17.00, duration: 0.25, maxSquint: 1.0 },
1061
+ { start: 18.05, duration: 0.12, maxSquint: 0.95 },
1062
+ { start: 19.50, duration: 0.12, maxSquint: 0.95 }
1063
+ ]);
1064
+
1065
+ function getBot6State(time) {
1066
+ var t = ((time % LOOP) + LOOP) % LOOP;
1067
+ var res = bot6Timeline.evaluate(t);
1068
+ var pose = res.pose;
1069
+ var squint = bot6Blinks.evaluate(t);
1070
+
1071
+ var splitL = pose.splitL || 0.0;
1072
+ var splitR = pose.splitR || 0.0;
1073
+ var maxSplit = Math.max(splitL, splitR);
1074
+
1075
+ var omegaV = 3.649 * Math.PI * 2.0;
1076
+ var omegaH = 1.820 * Math.PI * 2.0;
1077
+ var waveY = Math.sin(t * omegaV) * 0.015 * maxSplit;
1078
+ var waveYL = Math.sin((t + 0.12) * omegaV) * 0.015 * splitL;
1079
+ var waveYR = Math.sin((t - 0.12) * omegaV) * 0.015 * splitR;
1080
+
1081
+ var swayXC = Math.sin(t * omegaH) * 0.0220 * maxSplit;
1082
+ var swayXL = Math.sin((t + 0.12) * omegaH) * 0.0220 * splitL;
1083
+ var swayXR = Math.sin((t - 0.12) * omegaH) * 0.0220 * splitR;
1084
+
1085
+ var centerShiftX = (splitL > 0.01 && splitR < 0.5) ? +0.018 * splitL : ((splitR > 0.01 && splitL < 0.5) ? -0.018 * splitR : 0.0);
1086
+ var sepDist = 0.258;
1087
+ var currentScale = pose.scale;
1088
+
1089
+ var botCenter = {
1090
+ visible: true,
1091
+ scale: currentScale,
1092
+ scaleX: pose.scaleX || 1.0,
1093
+ scaleY: pose.scaleY || 1.0,
1094
+ x: swayXC + centerShiftX,
1095
+ y: (pose.jumpY || 0.0) + waveY,
1096
+ yaw: pose.yaw || 0.0,
1097
+ pitch: pose.pitch || 0.0,
1098
+ roll: pose.roll || 0.0,
1099
+ eyeShiftX: pose.eyeShiftX || 0.0,
1100
+ eyeShiftY: pose.eyeShiftY || 0.0,
1101
+ eyeScaleX: 1.0,
1102
+ eyeScaleY: 1.0,
1103
+ eyeGap: 0.27,
1104
+ bulge: pose.bulge || 0.0,
1105
+ squint: squint,
1106
+ bodyColor: 0x222126,
1107
+ eyeColor: 0xffffff,
1108
+ splitL: splitL,
1109
+ splitR: splitR
1110
+ };
1111
+
1112
+ var spawnXL = -(0.11 + (sepDist - 0.11) * splitL);
1113
+ var ghostL = {
1114
+ visible: splitL > 0.01,
1115
+ scale: 0.46 * splitL,
1116
+ scaleX: 1.0,
1117
+ scaleY: 1.0,
1118
+ x: spawnXL + swayXL,
1119
+ y: waveYL,
1120
+ yaw: (pose.yaw || 0.0) * 0.5,
1121
+ pitch: (pose.pitch || 0.0) * 0.5,
1122
+ roll: (pose.roll || 0.0) * 0.5 - 0.03 * splitL,
1123
+ eyeShiftX: 0.0,
1124
+ eyeShiftY: 0.0,
1125
+ eyeScaleX: 0.85,
1126
+ eyeScaleY: 0.85,
1127
+ eyeGap: 0.27,
1128
+ bulge: 0.0,
1129
+ squint: squint,
1130
+ bodyColor: 0x8229cc,
1131
+ eyeColor: 0xe8cbfa
1132
+ };
1133
+
1134
+ var spawnXR = +(0.11 + (sepDist - 0.11) * splitR);
1135
+ var ghostR = {
1136
+ visible: splitR > 0.01,
1137
+ scale: 0.46 * splitR,
1138
+ scaleX: 1.0,
1139
+ scaleY: 1.0,
1140
+ x: spawnXR + swayXR,
1141
+ y: waveYR,
1142
+ yaw: (pose.yaw || 0.0) * 0.5,
1143
+ pitch: (pose.pitch || 0.0) * 0.5,
1144
+ roll: (pose.roll || 0.0) * 0.5 + 0.03 * splitR,
1145
+ eyeShiftX: 0.0,
1146
+ eyeShiftY: 0.0,
1147
+ eyeScaleX: 0.85,
1148
+ eyeScaleY: 0.85,
1149
+ eyeGap: 0.27,
1150
+ bulge: 0.0,
1151
+ squint: squint,
1152
+ bodyColor: 0x47e4ad,
1153
+ eyeColor: 0xaff5dc
1154
+ };
1155
+
1156
+ return {
1157
+ botId: 6,
1158
+ type: 'bot6',
1159
+ label: res.label,
1160
+ botCenter: botCenter,
1161
+ ghostL: ghostL,
1162
+ ghostR: ghostR,
1163
+ splitL: splitL,
1164
+ splitR: splitR,
1165
+ dots: []
1166
+ };
1167
+ }
1168
+
1169
+ // --- #7 Satellite (环绕卫星) ---
1170
+ var bot7Timeline = new PoseTimeline([
1171
+ { t: 0.00, label: 'solid-upright', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 0.20, bulge: 0.0, jumpY: 0.0, squint: 0.75, showEyes: true } },
1172
+ { t: 0.06, label: 'loop-blink-open', ease: 'linear', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1173
+ { t: 1.30, label: 'solid-idle-1', ease: 'linear', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1174
+ { t: 1.45, label: 'curious-look-left-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.26, pitch: 0.0, roll: -0.14, eyeShiftX: -0.045, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1175
+ { t: 2.00, label: 'curious-look-left-1', ease: 'linear', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.26, pitch: 0.0, roll: -0.14, eyeShiftX: -0.045, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1176
+ { t: 2.12, label: 'squash-prep-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.05, scaleY: 0.95, yaw: 0.0, pitch: 0.01, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: -0.01, eyeScaleX: 1.05, eyeScaleY: 0.95, bulge: 0.0, jumpY: -0.008, squint: 0.0, showEyes: true } },
1177
+ { t: 2.24, label: 'anticipation-squash-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.22, scaleY: 0.78, yaw: 0.0, pitch: 0.02, roll: 0.0, eyeShiftX: -0.015, eyeShiftY: -0.025, eyeScaleX: 1.18, eyeScaleY: 0.75, bulge: 0.0, jumpY: -0.024, squint: 0.0, showEyes: true } },
1178
+ { t: 2.40, label: 'anticipation-stretch-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.96, scaleX: 0.88, scaleY: 1.18, yaw: 0.0, pitch: -0.04, roll: 0.36, eyeShiftX: -0.020, eyeShiftY: 0.035, eyeScaleX: 0.92, eyeScaleY: 1.12, bulge: 0.0, jumpY: 0.035, squint: 0.0, showEyes: true } },
1179
+ { t: 2.52, label: 'collapse-shrink-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.65, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.08, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 0.95, eyeScaleY: 0.95, bulge: 0.0, jumpY: 0.015, squint: 0.0, showEyes: false } },
1180
+ { t: 2.56, label: 'collapse-core-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: false } },
1181
+ { t: 2.80, label: 'satellite-burst-1', ease: 'linear', pose: { ringExpand: 1.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1182
+ { t: 7.04, label: 'satellite-orbit-1', ease: 'linear', pose: { ringExpand: 1.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1183
+ { t: 7.30, label: 'suction-absorb-1', ease: 'linear', pose: { ringExpand: 0.0, scale: 0.72, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.02, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: -0.005, squint: 0.0, showEyes: false } },
1184
+ { t: 7.46, label: 'expand-shoot-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 0.88, scaleY: 1.18, yaw: 0.0, pitch: -0.03, roll: -0.32, eyeShiftX: 0.02, eyeShiftY: 0.035, eyeScaleX: 0.95, eyeScaleY: 1.10, bulge: 0.0, jumpY: 0.035, squint: 0.0, showEyes: true } },
1185
+ { t: 7.60, label: 'expand-land-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.12, scaleY: 0.90, yaw: 0.0, pitch: 0.0, roll: -0.04, eyeShiftX: -0.02, eyeShiftY: -0.01, eyeScaleX: 1.05, eyeScaleY: 0.95, bulge: 0.0, jumpY: -0.014, squint: 0.0, showEyes: true } },
1186
+ { t: 7.72, label: 'settle-left-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.12, pitch: 0.0, roll: 0.0, eyeShiftX: -0.035, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1187
+ { t: 7.86, label: 'sleepy-prep-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.02, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1188
+ { t: 7.98, label: 'sleepy-slits-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.08, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 0.88, eyeScaleY: 0.12, bulge: 0.0, jumpY: 0.0, squint: 0.95, showEyes: true } },
1189
+ { t: 8.06, label: 'sleepy-slits-hold-1', ease: 'linear', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.08, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 0.88, eyeScaleY: 0.12, bulge: 0.0, jumpY: 0.0, squint: 0.95, showEyes: true } },
1190
+ { t: 8.18, label: 'curious-look-right-1', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.32, pitch: -0.04, roll: 0.02, eyeShiftX: 0.048, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1191
+ { t: 10.00, label: 'curious-look-right-1', ease: 'linear', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.32, pitch: -0.04, roll: 0.02, eyeShiftX: 0.048, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1192
+ { t: 10.12, label: 'squash-prep-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.06, scaleY: 0.94, yaw: 0.08, pitch: 0.01, roll: 0.0, eyeShiftX: 0.01, eyeShiftY: -0.01, eyeScaleX: 1.05, eyeScaleY: 0.95, bulge: 0.0, jumpY: -0.008, squint: 0.0, showEyes: true } },
1193
+ { t: 10.23, label: 'anticipation-squash-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.22, scaleY: 0.78, yaw: 0.0, pitch: 0.02, roll: 0.0, eyeShiftX: -0.015, eyeShiftY: -0.025, eyeScaleX: 1.18, eyeScaleY: 0.75, bulge: 0.0, jumpY: -0.024, squint: 0.0, showEyes: true } },
1194
+ { t: 10.40, label: 'anticipation-stretch-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.96, scaleX: 0.88, scaleY: 1.18, yaw: 0.0, pitch: -0.04, roll: 0.36, eyeShiftX: -0.020, eyeShiftY: 0.035, eyeScaleX: 0.92, eyeScaleY: 1.12, bulge: 0.0, jumpY: 0.035, squint: 0.0, showEyes: true } },
1195
+ { t: 10.52, label: 'collapse-shrink-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.65, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.08, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 0.95, eyeScaleY: 0.95, bulge: 0.0, jumpY: 0.015, squint: 0.0, showEyes: false } },
1196
+ { t: 10.56, label: 'collapse-core-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: false } },
1197
+ { t: 10.80, label: 'satellite-burst-2', ease: 'linear', pose: { ringExpand: 1.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1198
+ { t: 15.04, label: 'satellite-orbit-2', ease: 'linear', pose: { ringExpand: 1.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1199
+ { t: 15.30, label: 'suction-absorb-2', ease: 'linear', pose: { ringExpand: 0.0, scale: 0.72, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.02, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: -0.005, squint: 0.0, showEyes: false } },
1200
+ { t: 15.46, label: 'expand-shoot-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 0.88, scaleY: 1.18, yaw: 0.0, pitch: -0.03, roll: -0.32, eyeShiftX: 0.02, eyeShiftY: 0.035, eyeScaleX: 0.95, eyeScaleY: 1.10, bulge: 0.0, jumpY: 0.035, squint: 0.0, showEyes: true } },
1201
+ { t: 15.62, label: 'expand-land-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.12, scaleY: 0.90, yaw: 0.0, pitch: 0.0, roll: -0.04, eyeShiftX: -0.02, eyeShiftY: -0.01, eyeScaleX: 1.05, eyeScaleY: 0.95, bulge: 0.0, jumpY: -0.014, squint: 0.0, showEyes: true } },
1202
+ { t: 15.72, label: 'settle-left-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: -0.12, pitch: 0.0, roll: 0.0, eyeShiftX: -0.035, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1203
+ { t: 15.86, label: 'sleepy-prep-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.02, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1204
+ { t: 15.98, label: 'sleepy-slits-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.08, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 0.88, eyeScaleY: 0.12, bulge: 0.0, jumpY: 0.0, squint: 0.95, showEyes: true } },
1205
+ { t: 16.06, label: 'sleepy-slits-hold-2', ease: 'linear', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: -0.08, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 0.88, eyeScaleY: 0.12, bulge: 0.0, jumpY: 0.0, squint: 0.95, showEyes: true } },
1206
+ { t: 16.18, label: 'curious-look-right-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.32, pitch: -0.04, roll: 0.02, eyeShiftX: 0.048, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1207
+ { t: 16.24, label: 'glance-blink-prep-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.32, pitch: -0.04, roll: 0.02, eyeShiftX: 0.048, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1208
+ { t: 16.32, label: 'glance-blink-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.32, pitch: -0.04, roll: 0.02, eyeShiftX: 0.048, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 0.12, bulge: 0.0, jumpY: 0.0, squint: 0.85, showEyes: true } },
1209
+ { t: 16.40, label: 'glance-blink-open-2', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.32, pitch: -0.04, roll: 0.02, eyeShiftX: 0.048, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1210
+ { t: 17.80, label: 'curious-look-right-2', ease: 'linear', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.0, scaleY: 1.0, yaw: 0.32, pitch: -0.04, roll: 0.02, eyeShiftX: 0.048, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1211
+ { t: 18.12, label: 'squash-prep-3', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.06, scaleY: 0.94, yaw: 0.28, pitch: 0.01, roll: 0.0, eyeShiftX: 0.042, eyeShiftY: -0.01, eyeScaleX: 1.05, eyeScaleY: 0.95, bulge: 0.0, jumpY: -0.008, squint: 0.0, showEyes: true } },
1212
+ { t: 18.22, label: 'anticipation-squash-3', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 1.0, scaleX: 1.22, scaleY: 0.78, yaw: 0.22, pitch: 0.02, roll: 0.0, eyeShiftX: 0.040, eyeShiftY: -0.025, eyeScaleX: 1.18, eyeScaleY: 0.75, bulge: 0.0, jumpY: -0.024, squint: 0.0, showEyes: true } },
1213
+ { t: 18.38, label: 'anticipation-stretch-3', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.96, scaleX: 0.88, scaleY: 1.18, yaw: 0.0, pitch: -0.04, roll: 0.36, eyeShiftX: -0.020, eyeShiftY: 0.035, eyeScaleX: 0.92, eyeScaleY: 1.12, bulge: 0.0, jumpY: 0.035, squint: 0.0, showEyes: true } },
1214
+ { t: 18.50, label: 'collapse-shrink-3', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.65, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.08, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 0.95, eyeScaleY: 0.95, bulge: 0.0, jumpY: 0.015, squint: 0.0, showEyes: false } },
1215
+ { t: 18.56, label: 'collapse-core-3', ease: 'easeInOutQuad', pose: { ringExpand: 0.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: 0.0, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.0, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: false } },
1216
+ { t: 18.80, label: 'satellite-burst-3', ease: 'linear', pose: { ringExpand: 1.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } },
1217
+ { t: 20.783, label: 'satellite-orbit-3', ease: 'linear', pose: { ringExpand: 1.0, scale: 0.52, scaleX: 1.0, scaleY: 1.0, yaw: 0.0, pitch: -0.02, roll: 0.0, eyeShiftX: 0.0, eyeShiftY: 0.01, eyeScaleX: 1.0, eyeScaleY: 1.0, bulge: 0.0, jumpY: 0.0, squint: 0.0, showEyes: true } }
1218
+ ]);
1219
+
1220
+ var SATELLITE_RING_OFFSETS = [
1221
+ { x: 0, y: -46.0 },
1222
+ { x: 45.4, y: -46.0 },
1223
+ { x: 45.4, y: 0 },
1224
+ { x: 45.4, y: 46.0 },
1225
+ { x: 0, y: 46.0 },
1226
+ { x: -45.4, y: 46.0 },
1227
+ { x: -45.4, y: 0 },
1228
+ { x: -45.4, y: -46.0 }
1229
+ ];
1230
+
1231
+ var ORBIT_PERIOD = 3.600;
1232
+ var CAM_YAW_AMP = 0.45;
1233
+ var CAM_PITCH_AMP = 0.35;
1234
+ var CAM_PITCH_NEUTRAL = -0.10;
1235
+ var CAM_AZIMUTH_OFFSET = -0.118;
1236
+ var CAM_ELEVATION_OFFSET = 0.146;
1237
+
1238
+ function getBot7State(time) {
1239
+ var t = ((time % LOOP) + LOOP) % LOOP;
1240
+ var res = bot7Timeline.evaluate(t);
1241
+ var pose = res.pose;
1242
+ var ringExpand = pose.ringExpand;
1243
+
1244
+ var tOrbitStart = 3.20;
1245
+ if (t >= 17.0) {
1246
+ tOrbitStart = 19.20;
1247
+ } else if (t >= 9.0) {
1248
+ tOrbitStart = 11.20;
1249
+ }
1250
+
1251
+ var tOrbit = ((t - tOrbitStart) % ORBIT_PERIOD + ORBIT_PERIOD) % ORBIT_PERIOD;
1252
+ var waveAngle = - (tOrbit / ORBIT_PERIOD) * Math.PI * 2 - Math.PI / 2;
1253
+
1254
+ var targetDirX = Math.cos(waveAngle);
1255
+ var targetDirY = Math.sin(waveAngle);
1256
+
1257
+ var camYaw = targetDirX * CAM_YAW_AMP * ringExpand;
1258
+ var camPitch = (CAM_PITCH_NEUTRAL - targetDirY * CAM_PITCH_AMP) * ringExpand;
1259
+
1260
+ var trackingYaw = camYaw + CAM_AZIMUTH_OFFSET * ringExpand;
1261
+ var trackingPitch = camPitch - CAM_ELEVATION_OFFSET * ringExpand;
1262
+
1263
+ var hoverY = Math.sin((tOrbit / ORBIT_PERIOD) * 2 * Math.PI) * 0.006 * ringExpand;
1264
+ var hoverYPx = hoverY * 500.0;
1265
+ var dynamicRoll = targetDirX * 0.04 * ringExpand;
1266
+
1267
+ var dots = [];
1268
+ if (ringExpand > 0.001) {
1269
+ var dotDist = (35.0 + 10.4 * ringExpand) / 45.4;
1270
+ for (var i = 0; i < 8; i++) {
1271
+ var off = SATELLITE_RING_OFFSETS[i];
1272
+ var dotAngle = Math.atan2(-off.y, off.x);
1273
+ var diff = Math.cos(waveAngle - dotAngle);
1274
+ var pulse = Math.pow(Math.max(0, diff), 6);
1275
+ var r = ringExpand * (7.0 + pulse * 4.5);
1276
+ dots.push({
1277
+ x: off.x * dotDist,
1278
+ y: off.y * dotDist - hoverYPx,
1279
+ r: r,
1280
+ visible: true
1281
+ });
1282
+ }
1283
+ }
1284
+
1285
+ var bot = {
1286
+ visible: true,
1287
+ scale: pose.scale,
1288
+ scaleX: pose.scaleX,
1289
+ scaleY: pose.scaleY,
1290
+ x: 0.0,
1291
+ y: (pose.jumpY || 0.0) + hoverY,
1292
+ yaw: (pose.yaw || 0.0) + trackingYaw,
1293
+ pitch: (pose.pitch || 0.0) + trackingPitch,
1294
+ roll: (pose.roll || 0.0) + dynamicRoll,
1295
+ eyeShiftX: pose.eyeShiftX || 0.0,
1296
+ eyeShiftY: pose.eyeShiftY || 0.0,
1297
+ eyeScaleX: pose.eyeScaleX,
1298
+ eyeScaleY: pose.eyeScaleY,
1299
+ eyeGap: 0.27,
1300
+ bulge: pose.bulge,
1301
+ squint: pose.squint || 0.0,
1302
+ showEyes: pose.showEyes !== false,
1303
+ bodyColor: 0x222126,
1304
+ eyeColor: 0xffffff
1305
+ };
1306
+
1307
+ return { botId: 7, type: 'bot7', label: res.label, bot: bot, dots: dots };
1308
+ }
1309
+
1310
+ /* =========================================================================
1311
+ 4. Bot Catalog Registry & State Dispatcher
1312
+ ========================================================================= */
1313
+ var BOTS = [
1314
+ { id: 1, key: 'sentry', name: 'Sentry', title: '哨兵', desc: '逐行专注扫读、机灵侧倾 (代码审视/终端监控)', stateFn: getBot1State },
1315
+ { id: 2, key: 'attitude', name: 'Attitude', title: '傲娇小怪', desc: '3D后空翻、单侧挑眉与犀利聚焦 (AI拟人化助手/成功反馈)', stateFn: getBot2State },
1316
+ { id: 3, key: 'chameleon', name: 'Chameleon', title: '动感变色龙', desc: '240bpm 高频街舞摇摆、12色轮转 (音乐播放器/动感加载)', stateFn: getBot3State },
1317
+ { id: 4, key: 'observer', name: 'Observer', title: '屏读观察者', desc: '纯水平左-中-右栅格阅读扫读 (屏幕扫描/数据同步)', stateFn: getBot4State },
1318
+ { id: 5, key: 'cube', name: 'Cube & Grid', title: '方块与点阵', desc: '3x3呼吸点阵破土自旋跳跃、大眼暴胀 (启动/生成态/弹窗)', stateFn: getBot5State },
1319
+ { id: 6, key: 'ghost', name: 'Ghost Trail', title: '幽灵分身', desc: '身后诞生紫青双分身、蛇形浮游共舞 (多线程运算/集群协同)', stateFn: getBot6State },
1320
+ { id: 7, key: 'satellite', name: 'Satellite', title: '环绕卫星', desc: '核心塌陷蓄力、8卫星逆时针公转与3D注视 (网络加载/能量缓冲)', stateFn: getBot7State }
1321
+ ];
1322
+
1323
+ function resolveBotId(idOrKey) {
1324
+ if (typeof idOrKey === 'number') return idOrKey;
1325
+ if (typeof idOrKey === 'string') {
1326
+ var lower = idOrKey.toLowerCase().trim();
1327
+ var num = parseInt(lower.replace('#', ''), 10);
1328
+ if (!isNaN(num) && num >= 1 && num <= 7) return num;
1329
+ for (var i = 0; i < BOTS.length; i++) {
1330
+ if (BOTS[i].key === lower || BOTS[i].name.toLowerCase() === lower) {
1331
+ return BOTS[i].id;
1332
+ }
1333
+ }
1334
+ }
1335
+ return 1;
1336
+ }
1337
+
1338
+ function getBotState(botId, time) {
1339
+ var id = resolveBotId(botId);
1340
+ switch (id) {
1341
+ case 1: return getBot1State(time);
1342
+ case 2: return getBot2State(time);
1343
+ case 3: return getBot3State(time);
1344
+ case 4: return getBot4State(time);
1345
+ case 5: return getBot5State(time);
1346
+ case 6: return getBot6State(time);
1347
+ case 7: return getBot7State(time);
1348
+ default: return getBot1State(time);
1349
+ }
1350
+ }
1351
+
1352
+ /* =========================================================================
1353
+ 5. High-Level Mount Renderer & Player Controller
1354
+ ========================================================================= */
1355
+ var stageCounter = 0;
1356
+
1357
+ function mount(container, options) {
1358
+ if (!container) {
1359
+ throw new Error('[OpenBotMotion] mount requires a valid DOM element.');
1360
+ }
1361
+ options = options || {};
1362
+
1363
+ var activeBotId = resolveBotId(options.bot !== undefined ? options.bot : 1);
1364
+ var size = options.size !== undefined ? options.size : 280;
1365
+ var autoplay = options.autoplay !== undefined ? options.autoplay : true;
1366
+ var loop = options.loop !== undefined ? options.loop : true;
1367
+ var speed = options.speed !== undefined ? options.speed : 1.0;
1368
+ var onFrame = typeof options.onFrame === 'function' ? options.onFrame : null;
1369
+
1370
+ container.innerHTML = '';
1371
+ var uid = ++stageCounter;
1372
+ var svgNS = 'http://www.w3.org/2000/svg';
1373
+
1374
+ var svg = document.createElementNS(svgNS, 'svg');
1375
+ svg.setAttribute('class', 'open-bot-motion-svg');
1376
+ svg.setAttribute('viewBox', '-140 -140 280 280');
1377
+ svg.setAttribute('width', typeof size === 'number' ? (size + 'px') : size);
1378
+ svg.setAttribute('height', typeof size === 'number' ? (size + 'px') : size);
1379
+ svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
1380
+ container.appendChild(svg);
1381
+
1382
+ var defs = document.createElementNS(svgNS, 'defs');
1383
+ svg.appendChild(defs);
1384
+
1385
+ var filter = document.createElementNS(svgNS, 'filter');
1386
+ filter.setAttribute('id', 'obm-gooey-' + uid);
1387
+ filter.setAttribute('x', '-50%');
1388
+ filter.setAttribute('y', '-50%');
1389
+ filter.setAttribute('width', '200%');
1390
+ filter.setAttribute('height', '200%');
1391
+ filter.innerHTML =
1392
+ '<feGaussianBlur in="SourceGraphic" stdDeviation="5.5" result="blur" />' +
1393
+ '<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" result="goo" />' +
1394
+ '<feComposite in="SourceGraphic" in2="goo" operator="atop" />';
1395
+ defs.appendChild(filter);
1396
+
1397
+ function createClipDef(clipId, pathId) {
1398
+ var cp = document.createElementNS(svgNS, 'clipPath');
1399
+ cp.setAttribute('id', clipId);
1400
+ var p = document.createElementNS(svgNS, 'path');
1401
+ p.setAttribute('id', pathId);
1402
+ cp.appendChild(p);
1403
+ defs.appendChild(cp);
1404
+ return p;
1405
+ }
1406
+
1407
+ var clipPathCenter = createClipDef('obm-clip-center-' + uid, 'obm-cpp-center-' + uid);
1408
+ var clipPathGhostL = createClipDef('obm-clip-ghostL-' + uid, 'obm-cpp-ghostL-' + uid);
1409
+ var clipPathGhostR = createClipDef('obm-clip-ghostR-' + uid, 'obm-cpp-ghostR-' + uid);
1410
+
1411
+ var dotsLayer = document.createElementNS(svgNS, 'g');
1412
+ dotsLayer.setAttribute('class', 'dots-layer');
1413
+ svg.appendChild(dotsLayer);
1414
+
1415
+ var botLayer = document.createElementNS(svgNS, 'g');
1416
+ botLayer.setAttribute('class', 'bot-layer');
1417
+ svg.appendChild(botLayer);
1418
+
1419
+ var bodiesLayer = document.createElementNS(svgNS, 'g');
1420
+ bodiesLayer.setAttribute('class', 'bodies-layer');
1421
+ botLayer.appendChild(bodiesLayer);
1422
+
1423
+ var eyesLayer = document.createElementNS(svgNS, 'g');
1424
+ eyesLayer.setAttribute('class', 'eyes-layer');
1425
+ botLayer.appendChild(eyesLayer);
1426
+
1427
+ function createBotNode(clipId, defaultColor) {
1428
+ var bodyPath = document.createElementNS(svgNS, 'path');
1429
+ bodyPath.setAttribute('fill', defaultColor);
1430
+ bodiesLayer.appendChild(bodyPath);
1431
+
1432
+ var eyesGroup = document.createElementNS(svgNS, 'g');
1433
+ eyesGroup.setAttribute('clip-path', 'url(#' + clipId + ')');
1434
+
1435
+ var eyeL = document.createElementNS(svgNS, 'rect');
1436
+ eyeL.setAttribute('fill', '#ffffff');
1437
+ var eyeR = document.createElementNS(svgNS, 'rect');
1438
+ eyeR.setAttribute('fill', '#ffffff');
1439
+ eyesGroup.appendChild(eyeL);
1440
+ eyesGroup.appendChild(eyeR);
1441
+ eyesLayer.appendChild(eyesGroup);
1442
+
1443
+ return { bodyPath: bodyPath, eyesGroup: eyesGroup, eyeL: eyeL, eyeR: eyeR, defaultColor: defaultColor };
1444
+ }
1445
+
1446
+ var ghostLNode = createBotNode('obm-clip-ghostL-' + uid, '#8229cc');
1447
+ var ghostRNode = createBotNode('obm-clip-ghostR-' + uid, '#47e4ad');
1448
+ var centerNode = createBotNode('obm-clip-center-' + uid, CHARCOAL);
1449
+
1450
+ var projector = new SvgProjector({ viewportSize: 280 });
1451
+
1452
+ function colorToCss(c, def) {
1453
+ if (!c) return def;
1454
+ if (typeof c === 'number') return '#' + c.toString(16).padStart(6, '0');
1455
+ return c;
1456
+ }
1457
+
1458
+ function applyPose(node, clipPathEl, pose) {
1459
+ if (!node || !pose || pose.visible === false || (pose.scale !== undefined && pose.scale <= 0.001)) {
1460
+ if (node && node.bodyPath) node.bodyPath.style.display = 'none';
1461
+ if (node && node.eyesGroup) node.eyesGroup.style.display = 'none';
1462
+ if (clipPathEl) clipPathEl.setAttribute('d', '');
1463
+ return;
1464
+ }
1465
+ node.bodyPath.style.display = '';
1466
+
1467
+ var res = projector.projectRoundedCube(pose);
1468
+ if (!res.visible || !res.bodyPath) {
1469
+ node.bodyPath.style.display = 'none';
1470
+ node.eyesGroup.style.display = 'none';
1471
+ if (clipPathEl) clipPathEl.setAttribute('d', '');
1472
+ return;
1473
+ }
1474
+
1475
+ node.bodyPath.setAttribute('d', res.bodyPath);
1476
+ if (clipPathEl) clipPathEl.setAttribute('d', res.bodyPath);
1477
+
1478
+ var bodyFill = colorToCss(pose.bodyColor, node.defaultColor);
1479
+ var eyeFill = colorToCss(pose.eyeColor, '#ffffff');
1480
+ node.bodyPath.setAttribute('fill', bodyFill);
1481
+
1482
+ if (res.eyes && res.eyes.length > 0) {
1483
+ node.eyesGroup.style.display = '';
1484
+ while (node.eyesGroup.children.length < res.eyes.length) {
1485
+ node.eyesGroup.appendChild(document.createElementNS(svgNS, 'rect'));
1486
+ }
1487
+ while (node.eyesGroup.children.length > res.eyes.length) {
1488
+ node.eyesGroup.removeChild(node.eyesGroup.lastChild);
1489
+ }
1490
+
1491
+ for (var i = 0; i < res.eyes.length; i++) {
1492
+ var d = res.eyes[i];
1493
+ var rectEl = node.eyesGroup.children[i];
1494
+ rectEl.setAttribute('x', (-d.w / 2).toFixed(2));
1495
+ rectEl.setAttribute('y', (-d.h / 2).toFixed(2));
1496
+ rectEl.setAttribute('width', d.w.toFixed(2));
1497
+ rectEl.setAttribute('height', d.h.toFixed(2));
1498
+ rectEl.setAttribute('rx', d.rx.toFixed(2));
1499
+ rectEl.setAttribute('ry', d.ry.toFixed(2));
1500
+ rectEl.setAttribute('transform', 'translate(' + d.cx.toFixed(2) + ', ' + d.cy.toFixed(2) + ') rotate(' + d.angle.toFixed(2) + ')');
1501
+ rectEl.setAttribute('opacity', (d.opacity !== undefined ? d.opacity : 1.0).toFixed(2));
1502
+ rectEl.setAttribute('fill', colorToCss(d.color, eyeFill));
1503
+ }
1504
+ } else {
1505
+ node.eyesGroup.style.display = 'none';
1506
+ }
1507
+ }
1508
+
1509
+ function renderFrame(time) {
1510
+ var st = getBotState(activeBotId, time);
1511
+
1512
+ // Layer ordering
1513
+ if (activeBotId === 7 && svg.lastElementChild !== dotsLayer) {
1514
+ svg.appendChild(dotsLayer);
1515
+ } else if (activeBotId !== 7 && svg.firstElementChild !== dotsLayer) {
1516
+ svg.insertBefore(dotsLayer, botLayer);
1517
+ }
1518
+
1519
+ // Render dots layer
1520
+ if (st && st.dots && st.dots.length > 0) {
1521
+ dotsLayer.style.display = '';
1522
+ while (dotsLayer.children.length < st.dots.length) {
1523
+ dotsLayer.appendChild(document.createElementNS(svgNS, 'circle'));
1524
+ }
1525
+ while (dotsLayer.children.length > st.dots.length) {
1526
+ dotsLayer.removeChild(dotsLayer.lastElementChild);
1527
+ }
1528
+ for (var di = 0; di < st.dots.length; di++) {
1529
+ var dot = st.dots[di];
1530
+ var cEl = dotsLayer.children[di];
1531
+ if (!dot || !dot.visible || dot.r <= 0.1) {
1532
+ cEl.style.display = 'none';
1533
+ } else {
1534
+ cEl.style.display = '';
1535
+ cEl.setAttribute('cx', dot.x.toFixed(2));
1536
+ cEl.setAttribute('cy', dot.y.toFixed(2));
1537
+ cEl.setAttribute('r', dot.r.toFixed(2));
1538
+ cEl.setAttribute('fill', CHARCOAL);
1539
+ }
1540
+ }
1541
+ } else {
1542
+ dotsLayer.style.display = 'none';
1543
+ }
1544
+
1545
+ // Render bot bodies
1546
+ if (activeBotId === 6) {
1547
+ applyPose(ghostLNode, clipPathGhostL, st.ghostL);
1548
+ applyPose(ghostRNode, clipPathGhostR, st.ghostR);
1549
+ applyPose(centerNode, clipPathCenter, st.botCenter);
1550
+
1551
+ var maxSplit = Math.max(
1552
+ st.splitL !== undefined ? st.splitL : (st.botCenter && st.botCenter.splitL) || 0,
1553
+ st.splitR !== undefined ? st.splitR : (st.botCenter && st.botCenter.splitR) || 0
1554
+ );
1555
+ if (maxSplit > 0.08 && maxSplit < 0.92) {
1556
+ bodiesLayer.setAttribute('filter', 'url(#obm-gooey-' + uid + ')');
1557
+ } else {
1558
+ bodiesLayer.removeAttribute('filter');
1559
+ }
1560
+ } else {
1561
+ ghostLNode.bodyPath.style.display = 'none';
1562
+ ghostLNode.eyesGroup.style.display = 'none';
1563
+ ghostRNode.bodyPath.style.display = 'none';
1564
+ ghostRNode.eyesGroup.style.display = 'none';
1565
+ bodiesLayer.removeAttribute('filter');
1566
+ if (centerNode && st && st.bot) {
1567
+ applyPose(centerNode, clipPathCenter, st.bot);
1568
+ }
1569
+ }
1570
+
1571
+ if (onFrame) {
1572
+ onFrame(time, st.label, st);
1573
+ }
1574
+ }
1575
+
1576
+ // Playback state
1577
+ var currentTime = 0;
1578
+ var isPlaying = autoplay;
1579
+ var lastTimestamp = null;
1580
+ var rafId = null;
1581
+
1582
+ function tick(timestamp) {
1583
+ if (!lastTimestamp) lastTimestamp = timestamp;
1584
+ var dt = (timestamp - lastTimestamp) / 1000.0;
1585
+ lastTimestamp = timestamp;
1586
+
1587
+ if (isPlaying) {
1588
+ currentTime += dt * speed;
1589
+ if (loop) {
1590
+ currentTime = ((currentTime % LOOP) + LOOP) % LOOP;
1591
+ } else if (currentTime >= LOOP) {
1592
+ currentTime = LOOP;
1593
+ isPlaying = false;
1594
+ }
1595
+ }
1596
+
1597
+ renderFrame(currentTime);
1598
+
1599
+ if (isPlaying || loop) {
1600
+ rafId = requestAnimationFrame(tick);
1601
+ }
1602
+ }
1603
+
1604
+ renderFrame(0);
1605
+ if (autoplay) {
1606
+ rafId = requestAnimationFrame(tick);
1607
+ }
1608
+
1609
+ return {
1610
+ play: function () {
1611
+ if (!isPlaying) {
1612
+ isPlaying = true;
1613
+ lastTimestamp = null;
1614
+ rafId = requestAnimationFrame(tick);
1615
+ }
1616
+ },
1617
+ pause: function () {
1618
+ isPlaying = false;
1619
+ if (rafId) cancelAnimationFrame(rafId);
1620
+ },
1621
+ seek: function (t) {
1622
+ currentTime = clamp(t, 0, LOOP);
1623
+ renderFrame(currentTime);
1624
+ },
1625
+ setBot: function (idOrKey) {
1626
+ activeBotId = resolveBotId(idOrKey);
1627
+ renderFrame(currentTime);
1628
+ },
1629
+ setSpeed: function (s) {
1630
+ speed = Math.max(0.01, s);
1631
+ },
1632
+ setSize: function (newSize) {
1633
+ size = newSize;
1634
+ svg.setAttribute('width', typeof size === 'number' ? (size + 'px') : size);
1635
+ svg.setAttribute('height', typeof size === 'number' ? (size + 'px') : size);
1636
+ },
1637
+ getBotId: function () {
1638
+ return activeBotId;
1639
+ },
1640
+ getTime: function () {
1641
+ return currentTime;
1642
+ },
1643
+ isPlaying: function () {
1644
+ return isPlaying;
1645
+ },
1646
+ destroy: function () {
1647
+ if (rafId) cancelAnimationFrame(rafId);
1648
+ container.innerHTML = '';
1649
+ }
1650
+ };
1651
+ }
1652
+
1653
+ return {
1654
+ version: '1.0.0',
1655
+ LOOP: LOOP,
1656
+ BOTS: BOTS,
1657
+ Easings: Easings,
1658
+ PoseTimeline: PoseTimeline,
1659
+ BlinkTrack: BlinkTrack,
1660
+ DotGridRig: DotGridRig,
1661
+ SvgProjector: SvgProjector,
1662
+ resolveBotId: resolveBotId,
1663
+ getBotState: getBotState,
1664
+ getBot1State: getBot1State,
1665
+ getBot2State: getBot2State,
1666
+ getBot3State: getBot3State,
1667
+ getBot4State: getBot4State,
1668
+ getBot5State: getBot5State,
1669
+ getBot6State: getBot6State,
1670
+ getBot7State: getBot7State,
1671
+ mount: mount
1672
+ };
1673
+ }));