agent-robot-avatar 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +178 -30
- package/agent-robot-avatar.js +11 -1
- package/docs/README.de.md +8 -6
- package/docs/README.es.md +6 -8
- package/docs/README.fr.md +8 -8
- package/docs/README.ja.md +6 -8
- package/docs/README.ko.md +6 -8
- package/docs/README.pt.md +6 -8
- package/docs/README.zh-CN.md +194 -96
- package/docs/README.zh-TW.md +6 -8
- package/index.d.ts +30 -0
- package/package.json +1 -1
- package/src/agent-robot-avatar-antenna.js +24 -45
- package/src/agent-robot-avatar-geometry.js +40 -0
- package/src/agent-robot-avatar-head-roundness.js +2 -35
- package/src/agent-robot-avatar-inspect.js +37 -29
- package/src/agent-robot-avatar-runtime-fixes.js +178 -0
- package/src/agent-robot-avatar-runtime.js +744 -0
- package/src/agent-robot-avatar-version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -38,8 +38,38 @@ export type AgentRobotAvatarState =
|
|
|
38
38
|
| 'inspect'
|
|
39
39
|
| 'failure';
|
|
40
40
|
|
|
41
|
+
export type AgentRobotAvatarCanonicalAction =
|
|
42
|
+
| 'idle'
|
|
43
|
+
| 'bored'
|
|
44
|
+
| 'waiting'
|
|
45
|
+
| 'input'
|
|
46
|
+
| 'send'
|
|
47
|
+
| 'success'
|
|
48
|
+
| 'failure'
|
|
49
|
+
| 'warning'
|
|
50
|
+
| 'inspect'
|
|
51
|
+
| 'angry'
|
|
52
|
+
| 'blocked'
|
|
53
|
+
| 'error'
|
|
54
|
+
| 'surprise'
|
|
55
|
+
| 'sleep'
|
|
56
|
+
| 'wake'
|
|
57
|
+
| 'reaction';
|
|
58
|
+
|
|
59
|
+
export type AgentRobotAvatarActionPhase = 'start' | 'end' | 'cancel';
|
|
60
|
+
export type AgentRobotAvatarActionSource = 'api' | 'interaction' | 'automatic';
|
|
61
|
+
export type AgentRobotAvatarWakeOn = 'activity' | 'interaction' | 'manual';
|
|
62
|
+
export type AgentRobotAvatarMotion = 'auto' | 'reduce' | 'full';
|
|
63
|
+
|
|
64
|
+
export interface AgentRobotAvatarActionStateDetail {
|
|
65
|
+
action: AgentRobotAvatarCanonicalAction;
|
|
66
|
+
phase: AgentRobotAvatarActionPhase;
|
|
67
|
+
source: AgentRobotAvatarActionSource;
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
export interface AgentRobotAvatarEventMap extends HTMLElementEventMap {
|
|
42
71
|
'face-state': CustomEvent<{ state: AgentRobotAvatarState }>;
|
|
72
|
+
'action-state': CustomEvent<AgentRobotAvatarActionStateDetail>;
|
|
43
73
|
'head-roundness-change': CustomEvent<{ value: number; version: string }>;
|
|
44
74
|
}
|
|
45
75
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import AgentRobotAvatar, { registerAvatarExtension } from './agent-robot-avatar-extension-host.js';
|
|
2
|
+
import { DEFAULT_HEAD_HEIGHT_SCALE, flattenHeadPoints } from './agent-robot-avatar-geometry.js';
|
|
2
3
|
|
|
3
4
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
4
|
-
const HEAD_HEIGHT_SCALE = 0.9;
|
|
5
5
|
const ANTENNA_DEFAULTS = Object.freeze({
|
|
6
6
|
x: 120,
|
|
7
7
|
y: 12,
|
|
@@ -35,40 +35,10 @@ function antennaHome() {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function smooth01(t) {
|
|
39
|
-
t = Math.max(0, Math.min(1, t));
|
|
40
|
-
return t * t * (3 - 2 * t);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
function flattenHead(instance) {
|
|
44
39
|
if (instance._headFlattenedR32 || !instance._headShape || !instance._baseHeadPoints?.length) return;
|
|
45
40
|
|
|
46
|
-
const
|
|
47
|
-
const minY = Math.min(...source.map(point => point.y));
|
|
48
|
-
const maxY = Math.max(...source.map(point => point.y));
|
|
49
|
-
const height = maxY - minY;
|
|
50
|
-
const centerY = (minY + maxY) / 2;
|
|
51
|
-
const inset = height * (1 - HEAD_HEIGHT_SCALE) / 2;
|
|
52
|
-
|
|
53
|
-
const upperLockY = minY + height * 0.40;
|
|
54
|
-
const lowerLockY = maxY - height * 0.40;
|
|
55
|
-
|
|
56
|
-
const flattened = source.map(point => {
|
|
57
|
-
let shiftY = 0;
|
|
58
|
-
if (point.y <= upperLockY) {
|
|
59
|
-
shiftY = inset;
|
|
60
|
-
} else if (point.y < centerY) {
|
|
61
|
-
const t = (centerY - point.y) / Math.max(0.001, centerY - upperLockY);
|
|
62
|
-
shiftY = inset * smooth01(t);
|
|
63
|
-
} else if (point.y >= lowerLockY) {
|
|
64
|
-
shiftY = -inset;
|
|
65
|
-
} else if (point.y > centerY) {
|
|
66
|
-
const t = (point.y - centerY) / Math.max(0.001, lowerLockY - centerY);
|
|
67
|
-
shiftY = -inset * smooth01(t);
|
|
68
|
-
}
|
|
69
|
-
return { x: point.x, y: point.y + shiftY };
|
|
70
|
-
});
|
|
71
|
-
|
|
41
|
+
const flattened = flattenHeadPoints(instance._baseHeadPoints, DEFAULT_HEAD_HEIGHT_SCALE);
|
|
72
42
|
const path = typeof instance._pointsToPath === 'function'
|
|
73
43
|
? instance._pointsToPath(flattened)
|
|
74
44
|
: `M ${flattened.map((point, index) => `${index ? 'L ' : ''}${point.x.toFixed(2)} ${point.y.toFixed(2)}`).join(' ')} Z`;
|
|
@@ -135,24 +105,33 @@ function updateAntenna(instance, now) {
|
|
|
135
105
|
const dot = instance._antennaDot;
|
|
136
106
|
const target = antennaTarget(instance);
|
|
137
107
|
|
|
138
|
-
if (
|
|
108
|
+
if (instance._isReducedMotion?.()) {
|
|
139
109
|
spring.x = target.x;
|
|
140
110
|
spring.y = target.y;
|
|
111
|
+
spring.vx = 0;
|
|
112
|
+
spring.vy = 0;
|
|
141
113
|
spring.last = now;
|
|
142
114
|
spring.initialized = true;
|
|
143
|
-
}
|
|
115
|
+
} else {
|
|
116
|
+
if (!spring.initialized) {
|
|
117
|
+
spring.x = target.x;
|
|
118
|
+
spring.y = target.y;
|
|
119
|
+
spring.last = now;
|
|
120
|
+
spring.initialized = true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const dt = Math.max(0.001, Math.min(0.05, (now - spring.last) / 1000 || 0.033));
|
|
124
|
+
spring.last = now;
|
|
144
125
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
spring.x += spring.vx * dt;
|
|
155
|
-
spring.y += spring.vy * dt;
|
|
126
|
+
const stiffness = configNumber('stiffness', ANTENNA_DEFAULTS.stiffness, 5, 300);
|
|
127
|
+
const damping = configNumber('damping', ANTENNA_DEFAULTS.damping, 0.5, 40);
|
|
128
|
+
const ax = (target.x - spring.x) * stiffness - spring.vx * damping;
|
|
129
|
+
const ay = (target.y - spring.y) * stiffness - spring.vy * damping;
|
|
130
|
+
spring.vx += ax * dt;
|
|
131
|
+
spring.vy += ay * dt;
|
|
132
|
+
spring.x += spring.vx * dt;
|
|
133
|
+
spring.y += spring.vy * dt;
|
|
134
|
+
}
|
|
156
135
|
|
|
157
136
|
dot.setAttribute('cx', spring.x.toFixed(2));
|
|
158
137
|
dot.setAttribute('cy', spring.y.toFixed(2));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const DEFAULT_HEAD_HEIGHT_SCALE = 0.9;
|
|
2
|
+
|
|
3
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
4
|
+
const smooth01 = value => {
|
|
5
|
+
const t = clamp(value, 0, 1);
|
|
6
|
+
return t * t * (3 - 2 * t);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function flattenHeadPoints(points, heightScale = DEFAULT_HEAD_HEIGHT_SCALE) {
|
|
10
|
+
if (!points?.length) return [];
|
|
11
|
+
const source = points.map(point => ({ x: point.x, y: point.y }));
|
|
12
|
+
const minY = Math.min(...source.map(point => point.y));
|
|
13
|
+
const maxY = Math.max(...source.map(point => point.y));
|
|
14
|
+
const height = maxY - minY;
|
|
15
|
+
if (!(height > 0)) return source;
|
|
16
|
+
|
|
17
|
+
const scale = clamp(Number(heightScale), 0.1, 1);
|
|
18
|
+
const centerY = (minY + maxY) / 2;
|
|
19
|
+
const inset = height * (1 - scale) / 2;
|
|
20
|
+
const upperLockY = minY + height * 0.40;
|
|
21
|
+
const lowerLockY = maxY - height * 0.40;
|
|
22
|
+
|
|
23
|
+
return source.map(point => {
|
|
24
|
+
let shiftY = 0;
|
|
25
|
+
if (point.y <= upperLockY) {
|
|
26
|
+
shiftY = inset;
|
|
27
|
+
} else if (point.y < centerY) {
|
|
28
|
+
const t = (centerY - point.y) / Math.max(0.001, centerY - upperLockY);
|
|
29
|
+
shiftY = inset * smooth01(t);
|
|
30
|
+
} else if (point.y >= lowerLockY) {
|
|
31
|
+
shiftY = -inset;
|
|
32
|
+
} else if (point.y > centerY) {
|
|
33
|
+
const t = (point.y - centerY) / Math.max(0.001, lowerLockY - centerY);
|
|
34
|
+
shiftY = -inset * smooth01(t);
|
|
35
|
+
}
|
|
36
|
+
return { x: point.x, y: point.y + shiftY };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { DEFAULT_HEAD_HEIGHT_SCALE, flattenHeadPoints };
|
|
@@ -1,47 +1,14 @@
|
|
|
1
1
|
import AgentRobotAvatar from './agent-robot-avatar-extension-host.js';
|
|
2
|
+
import { flattenHeadPoints } from './agent-robot-avatar-geometry.js';
|
|
2
3
|
import { VERSION } from './agent-robot-avatar-version.js';
|
|
3
4
|
|
|
4
5
|
const proto = AgentRobotAvatar.prototype;
|
|
5
|
-
const HEAD_HEIGHT_SCALE = 0.9;
|
|
6
6
|
const DEFAULT_ROUNDNESS = 50;
|
|
7
7
|
|
|
8
8
|
function clamp(value, min, max) {
|
|
9
9
|
return Math.max(min, Math.min(max, value));
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function smooth01(t) {
|
|
13
|
-
t = clamp(t, 0, 1);
|
|
14
|
-
return t * t * (3 - 2 * t);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function flattenPoints(points) {
|
|
18
|
-
if (!points?.length) return [];
|
|
19
|
-
const source = points.map(point => ({ x: point.x, y: point.y }));
|
|
20
|
-
const minY = Math.min(...source.map(point => point.y));
|
|
21
|
-
const maxY = Math.max(...source.map(point => point.y));
|
|
22
|
-
const height = maxY - minY;
|
|
23
|
-
const centerY = (minY + maxY) / 2;
|
|
24
|
-
const inset = height * (1 - HEAD_HEIGHT_SCALE) / 2;
|
|
25
|
-
const upperLockY = minY + height * 0.40;
|
|
26
|
-
const lowerLockY = maxY - height * 0.40;
|
|
27
|
-
|
|
28
|
-
return source.map(point => {
|
|
29
|
-
let shiftY = 0;
|
|
30
|
-
if (point.y <= upperLockY) {
|
|
31
|
-
shiftY = inset;
|
|
32
|
-
} else if (point.y < centerY) {
|
|
33
|
-
const t = (centerY - point.y) / Math.max(0.001, centerY - upperLockY);
|
|
34
|
-
shiftY = inset * smooth01(t);
|
|
35
|
-
} else if (point.y >= lowerLockY) {
|
|
36
|
-
shiftY = -inset;
|
|
37
|
-
} else if (point.y > centerY) {
|
|
38
|
-
const t = (point.y - centerY) / Math.max(0.001, lowerLockY - centerY);
|
|
39
|
-
shiftY = -inset * smooth01(t);
|
|
40
|
-
}
|
|
41
|
-
return { x: point.x, y: point.y + shiftY };
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
12
|
function ensureDefaultGeometry(instance) {
|
|
46
13
|
if (!instance?._headShape || !instance._baseHeadPoints?.length) return false;
|
|
47
14
|
if (instance._headRoundnessDefaultPoints?.length) return true;
|
|
@@ -49,7 +16,7 @@ function ensureDefaultGeometry(instance) {
|
|
|
49
16
|
let points = instance._baseHeadPoints.map(point => ({ x: point.x, y: point.y }));
|
|
50
17
|
|
|
51
18
|
if (!instance._headFlattenedR32) {
|
|
52
|
-
points =
|
|
19
|
+
points = flattenHeadPoints(points);
|
|
53
20
|
instance._headFlattenedR32 = true;
|
|
54
21
|
instance._baseHeadPoints = points.map(point => ({ ...point }));
|
|
55
22
|
instance._baseHeadPathD = instance._pointsToPath(points);
|
|
@@ -31,46 +31,54 @@ const INSPECT_DEFAULTS = Object.freeze({
|
|
|
31
31
|
open: 260,
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
const INSPECT_BOUNDS = Object.freeze({
|
|
35
|
+
aperture: [6, 40],
|
|
36
|
+
scanOffset: [0, 24],
|
|
37
|
+
prepPause: [0, 2400],
|
|
38
|
+
close: [40, 2400],
|
|
39
|
+
holdClosed: [0, 2400],
|
|
40
|
+
down1: [40, 2400],
|
|
41
|
+
holdDown1: [0, 2400],
|
|
42
|
+
up1: [40, 2400],
|
|
43
|
+
holdUp1: [0, 2400],
|
|
44
|
+
center1: [40, 2400],
|
|
45
|
+
holdCenter: [0, 2400],
|
|
46
|
+
down2: [40, 2400],
|
|
47
|
+
holdDown2: [0, 2400],
|
|
48
|
+
up2: [40, 2400],
|
|
49
|
+
holdUp2: [0, 2400],
|
|
50
|
+
center2: [40, 2400],
|
|
51
|
+
holdAfter: [0, 2400],
|
|
52
|
+
open: [40, 2400],
|
|
53
|
+
});
|
|
54
|
+
|
|
34
55
|
const runtimeWindow = typeof window !== 'undefined' ? window : null;
|
|
35
56
|
const inspectConfig = (runtimeWindow?.AgentRobotAvatarInspectConfig && typeof runtimeWindow.AgentRobotAvatarInspectConfig === 'object')
|
|
36
57
|
? runtimeWindow.AgentRobotAvatarInspectConfig
|
|
37
58
|
: {};
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
|
|
60
|
+
function normalizeConfigValue(key, value) {
|
|
61
|
+
const fallback = INSPECT_DEFAULTS[key];
|
|
62
|
+
const numeric = Number(value);
|
|
63
|
+
if (!Number.isFinite(numeric)) return fallback;
|
|
64
|
+
const [min, max] = INSPECT_BOUNDS[key];
|
|
65
|
+
return Math.max(min, Math.min(max, numeric));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
69
|
+
inspectConfig[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
40
70
|
}
|
|
41
71
|
if (runtimeWindow) {
|
|
42
72
|
runtimeWindow.AgentRobotAvatarInspectConfig = inspectConfig;
|
|
43
73
|
runtimeWindow.AgentRobotAvatarInspectDefaults = INSPECT_DEFAULTS;
|
|
44
74
|
}
|
|
45
75
|
|
|
46
|
-
function configNumber(key, fallback, min, max) {
|
|
47
|
-
const value = Number(inspectConfig[key]);
|
|
48
|
-
if (!Number.isFinite(value)) return fallback;
|
|
49
|
-
return Math.max(min, Math.min(max, value));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
76
|
function currentInspectConfig() {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
close: Math.max(40, duration('close')),
|
|
59
|
-
holdClosed: duration('holdClosed'),
|
|
60
|
-
down1: Math.max(40, duration('down1')),
|
|
61
|
-
holdDown1: duration('holdDown1'),
|
|
62
|
-
up1: Math.max(40, duration('up1')),
|
|
63
|
-
holdUp1: duration('holdUp1'),
|
|
64
|
-
center1: Math.max(40, duration('center1')),
|
|
65
|
-
holdCenter: duration('holdCenter'),
|
|
66
|
-
down2: Math.max(40, duration('down2')),
|
|
67
|
-
holdDown2: duration('holdDown2'),
|
|
68
|
-
up2: Math.max(40, duration('up2')),
|
|
69
|
-
holdUp2: duration('holdUp2'),
|
|
70
|
-
center2: Math.max(40, duration('center2')),
|
|
71
|
-
holdAfter: duration('holdAfter'),
|
|
72
|
-
open: Math.max(40, duration('open')),
|
|
73
|
-
};
|
|
77
|
+
const config = {};
|
|
78
|
+
for (const key of Object.keys(INSPECT_DEFAULTS)) {
|
|
79
|
+
config[key] = normalizeConfigValue(key, inspectConfig[key]);
|
|
80
|
+
}
|
|
81
|
+
return config;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
function dispatchInspectState(instance, state) {
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import AgentRobotAvatar from './agent-robot-avatar-extension-host.js';
|
|
2
|
+
|
|
3
|
+
const proto = AgentRobotAvatar.prototype;
|
|
4
|
+
const baseUpdateDragJelly = proto._updateDragJelly;
|
|
5
|
+
const baseResumeFrames = proto._resumeFrames;
|
|
6
|
+
const baseCanPauseFrames = proto._canPauseFrames;
|
|
7
|
+
const baseDraw = proto._draw;
|
|
8
|
+
const baseIsReducedMotion = proto._isReducedMotion;
|
|
9
|
+
|
|
10
|
+
let currentEnvironmentEvent = null;
|
|
11
|
+
let clearEnvironmentTimer = 0;
|
|
12
|
+
|
|
13
|
+
function rememberEnvironmentEvent(event) {
|
|
14
|
+
currentEnvironmentEvent = event;
|
|
15
|
+
if (clearEnvironmentTimer) clearTimeout(clearEnvironmentTimer);
|
|
16
|
+
clearEnvironmentTimer = setTimeout(() => {
|
|
17
|
+
currentEnvironmentEvent = null;
|
|
18
|
+
clearEnvironmentTimer = 0;
|
|
19
|
+
}, 0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof document !== 'undefined') {
|
|
23
|
+
document.addEventListener('pointerdown', rememberEnvironmentEvent, true);
|
|
24
|
+
document.addEventListener('keydown', rememberEnvironmentEvent, true);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function wakePolicy(instance) {
|
|
28
|
+
const value = String(instance.getAttribute('wake-on') || 'activity').trim().toLowerCase();
|
|
29
|
+
return value === 'manual' || value === 'interaction' ? value : 'activity';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function eventTargetsAvatar(event, instance) {
|
|
33
|
+
const path = event?.composedPath?.();
|
|
34
|
+
if (Array.isArray(path)) return path.includes(instance);
|
|
35
|
+
return event?.target === instance || instance.contains?.(event?.target);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Keep the public noteActivity(wake?) contract, but distinguish calls made by
|
|
39
|
+
// the shared environment listeners from explicit API calls. Environment input
|
|
40
|
+
// follows wake-on; an explicit noteActivity() remains an intentional wake.
|
|
41
|
+
proto.noteActivity = function(wake = true) {
|
|
42
|
+
this._lastActivity = performance.now();
|
|
43
|
+
this._resumeFrames?.();
|
|
44
|
+
if (!wake || (!this._sleeping && this._state !== 'sleep')) return;
|
|
45
|
+
|
|
46
|
+
const event = currentEnvironmentEvent;
|
|
47
|
+
if (!event) {
|
|
48
|
+
this.wake();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const policy = wakePolicy(this);
|
|
53
|
+
const shouldWake = policy === 'activity' || (policy === 'interaction' && eventTargetsAvatar(event, this));
|
|
54
|
+
if (!shouldWake) return;
|
|
55
|
+
|
|
56
|
+
const previousSource = this._runtimeActionSource;
|
|
57
|
+
this._runtimeActionSource = 'automatic';
|
|
58
|
+
try {
|
|
59
|
+
this.wake();
|
|
60
|
+
} finally {
|
|
61
|
+
this._runtimeActionSource = previousSource;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function dragAtRest(drag) {
|
|
66
|
+
if (!drag || drag.active || drag.returning || drag.pendingReaction) return false;
|
|
67
|
+
return Math.abs(drag.x) < 0.0001 && Math.abs(drag.y) < 0.0001 &&
|
|
68
|
+
Math.abs(drag.vx) < 0.0001 && Math.abs(drag.vy) < 0.0001 &&
|
|
69
|
+
Math.abs(drag.stretch) < 0.0001 && Math.abs(drag.shear) < 0.0001 &&
|
|
70
|
+
Math.abs(drag.pullX) < 0.0001 && Math.abs(drag.pullY) < 0.0001;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Core's original jelly renderer writes an identity transform every frame even
|
|
74
|
+
// after reset. Clear that identity transform once the interaction is fully at
|
|
75
|
+
// rest so reset/disconnect leave no visual residue or unnecessary style churn.
|
|
76
|
+
proto._updateDragJelly = function(dt) {
|
|
77
|
+
const result = baseUpdateDragJelly.call(this, dt);
|
|
78
|
+
if (dragAtRest(this._dragJelly)) {
|
|
79
|
+
if (this._dragMotion) {
|
|
80
|
+
this._dragMotion.style.transform = '';
|
|
81
|
+
this._dragMotion.style.transformOrigin = '';
|
|
82
|
+
}
|
|
83
|
+
if (this._headShape && this._baseHeadPathD) {
|
|
84
|
+
this._headShape.setAttribute('d', this._baseHeadPathD);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function cancelContinuousMotion(instance) {
|
|
91
|
+
const animations = instance._headMotion?.getAnimations?.() || [];
|
|
92
|
+
for (const animation of animations) animation.cancel();
|
|
93
|
+
if (instance._headMotion) instance._headMotion.style.transform = '';
|
|
94
|
+
instance._blinkAnim = null;
|
|
95
|
+
instance._blink = 1;
|
|
96
|
+
instance._boredRoutine = null;
|
|
97
|
+
instance._boredLookSpeed = null;
|
|
98
|
+
if (instance._wanderTarget) instance._wanderTarget.x = instance._wanderTarget.y = 0;
|
|
99
|
+
if (instance._look) instance._look.x = instance._look.y = 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function requestedMotion(instance) {
|
|
103
|
+
const value = String(instance.getAttribute('motion') || 'auto').trim().toLowerCase();
|
|
104
|
+
return value === 'reduce' || value === 'full' ? value : 'auto';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function effectiveReducedMotion(instance) {
|
|
108
|
+
const requested = requestedMotion(instance);
|
|
109
|
+
if (requested === 'reduce') return true;
|
|
110
|
+
if (requested === 'full') return false;
|
|
111
|
+
|
|
112
|
+
// A fresh MediaQueryList avoids engine-specific delays where an older
|
|
113
|
+
// MediaQueryList object's matches value lags behind a runtime preference
|
|
114
|
+
// update. Fall back to the coordinator's original resolver when unavailable.
|
|
115
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
116
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
117
|
+
}
|
|
118
|
+
return baseIsReducedMotion.call(instance);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function syncMotionPreference(instance) {
|
|
122
|
+
const reduced = effectiveReducedMotion(instance);
|
|
123
|
+
if (instance._runtimeLastReducedMotion === reduced) return reduced;
|
|
124
|
+
instance._runtimeLastReducedMotion = reduced;
|
|
125
|
+
instance._runtimeVisualDirty = true;
|
|
126
|
+
if (reduced) cancelContinuousMotion(instance);
|
|
127
|
+
return reduced;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Some engines deliver matchMedia change events later than the next animation
|
|
131
|
+
// frame. Also sample the effective preference while frames are active so a
|
|
132
|
+
// runtime switch cannot leave an old Web Animation running until that event.
|
|
133
|
+
proto._isReducedMotion = function() {
|
|
134
|
+
return syncMotionPreference(this);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Reduced motion should render each newly reached static state once, then stop
|
|
138
|
+
// scheduling frames. Extension effects historically create some Web Animations
|
|
139
|
+
// directly instead of routing through _animateHead; cancel those in a microtask
|
|
140
|
+
// after an effect becomes active so ordinary-mode animation code stays intact.
|
|
141
|
+
for (const property of ['_waitingFx', '_inspectFx', '_failureFx', '_warningFx', '_systemErrorShake']) {
|
|
142
|
+
const slot = Symbol(property);
|
|
143
|
+
Object.defineProperty(proto, property, {
|
|
144
|
+
configurable: true,
|
|
145
|
+
get() { return this[slot]; },
|
|
146
|
+
set(value) {
|
|
147
|
+
this[slot] = value;
|
|
148
|
+
this._runtimeVisualDirty = true;
|
|
149
|
+
if (this.isConnected && this._isReducedMotion?.()) {
|
|
150
|
+
this._resumeFrames?.();
|
|
151
|
+
queueMicrotask(() => {
|
|
152
|
+
if (this.isConnected && this._isReducedMotion?.()) cancelContinuousMotion(this);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
proto._resumeFrames = function() {
|
|
160
|
+
if (this._isReducedMotion?.()) this._runtimeVisualDirty = true;
|
|
161
|
+
return baseResumeFrames.call(this);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
proto._draw = function(now) {
|
|
165
|
+
syncMotionPreference(this);
|
|
166
|
+
const result = baseDraw.call(this, now);
|
|
167
|
+
this._runtimeVisualDirty = false;
|
|
168
|
+
return result;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
proto._canPauseFrames = function(now) {
|
|
172
|
+
syncMotionPreference(this);
|
|
173
|
+
if (this._isReducedMotion?.() && this._runtimeVisualDirty) return false;
|
|
174
|
+
return baseCanPauseFrames.call(this, now);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export { AgentRobotAvatar };
|
|
178
|
+
export default AgentRobotAvatar;
|