let-them-talk 3.5.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,212 @@
1
+ import * as THREE from 'three';
2
+
3
+ // Build outfit overlays on top of the base character body.
4
+ // Returns a group that gets added to the character group.
5
+ // Outfit replaces/covers the base torso visually.
6
+ export function buildOutfit(style, colors, charGroup) {
7
+ var outfitGroup = new THREE.Group();
8
+ outfitGroup.userData.isOutfit = true;
9
+ var shirtHex = new THREE.Color(colors.shirt_color || '#58a6ff').getHex();
10
+ var pantsHex = new THREE.Color(colors.pants_color || '#2d3748').getHex();
11
+ var mat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.7 });
12
+ var mat2 = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.65 });
13
+
14
+ switch (style) {
15
+ case 'hoodie': {
16
+ // Hoodie body — slightly larger than torso, covers it
17
+ var hoodieMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.85 });
18
+ var hoodieBody = new THREE.Mesh(new THREE.BoxGeometry(0.34, 0.34, 0.21), hoodieMat);
19
+ hoodieBody.position.y = 0.58; hoodieBody.castShadow = true;
20
+ outfitGroup.add(hoodieBody);
21
+ // Hood (behind head)
22
+ var hood = new THREE.Mesh(new THREE.SphereGeometry(0.18, 12, 8, 0, Math.PI * 2, Math.PI * 0.3, Math.PI * 0.5), hoodieMat);
23
+ hood.position.set(0, 0.82, -0.08); hood.castShadow = true;
24
+ outfitGroup.add(hood);
25
+ // Kangaroo pocket
26
+ var pocketMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.9 });
27
+ pocketMat.color.multiplyScalar(0.85);
28
+ var pocket = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.1, 0.01), pocketMat);
29
+ pocket.position.set(0, 0.48, 0.11); pocket.castShadow = true;
30
+ outfitGroup.add(pocket);
31
+ // Drawstrings
32
+ var stringMat = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.5 });
33
+ [-0.04, 0.04].forEach(function(sx) {
34
+ var str = new THREE.Mesh(new THREE.CylinderGeometry(0.005, 0.005, 0.12, 4), stringMat);
35
+ str.position.set(sx, 0.7, 0.11);
36
+ outfitGroup.add(str);
37
+ });
38
+ // Arm sleeves (wider)
39
+ var sleeveMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.85 });
40
+ [-0.21, 0.21].forEach(function(sx) {
41
+ var sleeve = new THREE.Mesh(new THREE.BoxGeometry(0.11, 0.14, 0.11), sleeveMat);
42
+ sleeve.position.set(sx, 0.65, 0); sleeve.castShadow = true;
43
+ outfitGroup.add(sleeve);
44
+ });
45
+ break;
46
+ }
47
+ case 'suit': {
48
+ // Suit jacket — structured, slightly wider shoulders
49
+ var suitMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.5, metalness: 0.05 });
50
+ var jacket = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.34, 0.2), suitMat);
51
+ jacket.position.y = 0.58; jacket.castShadow = true;
52
+ outfitGroup.add(jacket);
53
+ // Lapels (v-shape on front)
54
+ var lapelMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.4 });
55
+ lapelMat.color.multiplyScalar(0.8);
56
+ var lapelGeo = new THREE.BufferGeometry();
57
+ var lv = new Float32Array([
58
+ -0.06, 0.08, 0.005, 0, -0.02, 0.005, 0, 0.08, 0.005,
59
+ ]);
60
+ lapelGeo.setAttribute('position', new THREE.BufferAttribute(lv, 3));
61
+ lapelGeo.computeVertexNormals();
62
+ var leftLapel = new THREE.Mesh(lapelGeo, lapelMat);
63
+ leftLapel.position.set(-0.05, 0.64, 0.1);
64
+ outfitGroup.add(leftLapel);
65
+ var rv = new Float32Array([
66
+ 0.06, 0.08, 0.005, 0, -0.02, 0.005, 0, 0.08, 0.005,
67
+ ]);
68
+ var rLapelGeo = new THREE.BufferGeometry();
69
+ rLapelGeo.setAttribute('position', new THREE.BufferAttribute(rv, 3));
70
+ rLapelGeo.computeVertexNormals();
71
+ var rightLapel = new THREE.Mesh(rLapelGeo, lapelMat);
72
+ rightLapel.position.set(0.05, 0.64, 0.1);
73
+ outfitGroup.add(rightLapel);
74
+ // White shirt underneath (visible strip)
75
+ var shirtStrip = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.2, 0.005), new THREE.MeshStandardMaterial({ color: 0xf0f0f0, roughness: 0.4 }));
76
+ shirtStrip.position.set(0, 0.56, 0.103);
77
+ outfitGroup.add(shirtStrip);
78
+ // Buttons
79
+ var btnMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3, metalness: 0.2 });
80
+ [0.6, 0.52].forEach(function(by) {
81
+ var btn = new THREE.Mesh(new THREE.SphereGeometry(0.01, 6, 4), btnMat);
82
+ btn.position.set(0, by, 0.105);
83
+ outfitGroup.add(btn);
84
+ });
85
+ // Shoulder pads
86
+ [-0.18, 0.18].forEach(function(sx) {
87
+ var pad = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.03, 0.16), suitMat);
88
+ pad.position.set(sx, 0.74, 0); pad.castShadow = true;
89
+ outfitGroup.add(pad);
90
+ });
91
+ break;
92
+ }
93
+ case 'dress': {
94
+ // Dress top (fitted torso)
95
+ var dressMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.6 });
96
+ var dressTop = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.3, 0.19), dressMat);
97
+ dressTop.position.y = 0.59; dressTop.castShadow = true;
98
+ outfitGroup.add(dressTop);
99
+ // Skirt (flared cylinder below torso)
100
+ var skirtGeo = new THREE.CylinderGeometry(0.12, 0.22, 0.22, 12);
101
+ var skirt = new THREE.Mesh(skirtGeo, dressMat);
102
+ skirt.position.y = 0.34; skirt.castShadow = true;
103
+ outfitGroup.add(skirt);
104
+ // Belt/waist band
105
+ var beltMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.4 });
106
+ beltMat.color.multiplyScalar(0.7);
107
+ var belt = new THREE.Mesh(new THREE.BoxGeometry(0.33, 0.03, 0.2), beltMat);
108
+ belt.position.y = 0.44; belt.castShadow = true;
109
+ outfitGroup.add(belt);
110
+ // Small bow at neckline
111
+ var bowMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.5 });
112
+ bowMat.color.multiplyScalar(0.75);
113
+ var bow = new THREE.Mesh(new THREE.SphereGeometry(0.02, 6, 4), bowMat);
114
+ bow.position.set(0, 0.74, 0.1);
115
+ outfitGroup.add(bow);
116
+ break;
117
+ }
118
+ case 'labcoat': {
119
+ // White lab coat over shirt
120
+ var coatMat = new THREE.MeshStandardMaterial({ color: 0xf5f5f0, roughness: 0.4 });
121
+ var coat = new THREE.Mesh(new THREE.BoxGeometry(0.36, 0.38, 0.21), coatMat);
122
+ coat.position.y = 0.56; coat.castShadow = true;
123
+ outfitGroup.add(coat);
124
+ // Coat extends lower (skirt portion)
125
+ var coatSkirt = new THREE.Mesh(new THREE.BoxGeometry(0.34, 0.12, 0.19), coatMat);
126
+ coatSkirt.position.y = 0.34; coatSkirt.castShadow = true;
127
+ outfitGroup.add(coatSkirt);
128
+ // Colored shirt underneath visible at collar
129
+ var underShirt = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.06, 0.005), new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.6 }));
130
+ underShirt.position.set(0, 0.72, 0.108);
131
+ outfitGroup.add(underShirt);
132
+ // Pocket (left breast)
133
+ var pocketMat2 = new THREE.MeshStandardMaterial({ color: 0xe8e8e0, roughness: 0.5 });
134
+ var pocket2 = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.06, 0.005), pocketMat2);
135
+ pocket2.position.set(-0.1, 0.62, 0.108);
136
+ outfitGroup.add(pocket2);
137
+ // Pen in pocket
138
+ var pen = new THREE.Mesh(new THREE.CylinderGeometry(0.004, 0.004, 0.06, 4), new THREE.MeshStandardMaterial({ color: 0x2244aa, roughness: 0.3 }));
139
+ pen.position.set(-0.08, 0.66, 0.112);
140
+ outfitGroup.add(pen);
141
+ // Coat sleeves (white, over arms)
142
+ [-0.22, 0.22].forEach(function(sx) {
143
+ var sleeve = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.16, 0.1), coatMat);
144
+ sleeve.position.set(sx, 0.64, 0); sleeve.castShadow = true;
145
+ outfitGroup.add(sleeve);
146
+ });
147
+ break;
148
+ }
149
+ case 'vest': {
150
+ // Sleeveless vest over shirt
151
+ var vestMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.55 });
152
+ var vestBody = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.33, 0.19), vestMat);
153
+ vestBody.position.y = 0.58; vestBody.castShadow = true;
154
+ outfitGroup.add(vestBody);
155
+ // V-neck opening showing shirt
156
+ var vShirt = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.12, 0.005), new THREE.MeshStandardMaterial({ color: 0xf0f0f0, roughness: 0.5 }));
157
+ vShirt.position.set(0, 0.66, 0.098);
158
+ outfitGroup.add(vShirt);
159
+ // Buttons
160
+ var vBtnMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3 });
161
+ [0.62, 0.56, 0.50].forEach(function(by) {
162
+ var btn = new THREE.Mesh(new THREE.SphereGeometry(0.008, 5, 4), vBtnMat);
163
+ btn.position.set(0.04, by, 0.1);
164
+ outfitGroup.add(btn);
165
+ });
166
+ break;
167
+ }
168
+ case 'jacket': {
169
+ // Casual zip-up jacket
170
+ var jacketMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.7 });
171
+ var jacketBody = new THREE.Mesh(new THREE.BoxGeometry(0.34, 0.34, 0.2), jacketMat);
172
+ jacketBody.position.y = 0.58; jacketBody.castShadow = true;
173
+ outfitGroup.add(jacketBody);
174
+ // Zipper (metallic line down center)
175
+ var zipMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.2, metalness: 0.6 });
176
+ var zip = new THREE.Mesh(new THREE.BoxGeometry(0.01, 0.28, 0.005), zipMat);
177
+ zip.position.set(0, 0.58, 0.105);
178
+ outfitGroup.add(zip);
179
+ // Zip pull tab
180
+ var tab = new THREE.Mesh(new THREE.BoxGeometry(0.015, 0.02, 0.008), zipMat);
181
+ tab.position.set(0, 0.66, 0.11);
182
+ outfitGroup.add(tab);
183
+ // Collar (standing)
184
+ var collarMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.65 });
185
+ collarMat.color.multiplyScalar(0.9);
186
+ var collar = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.06, 0.16), collarMat);
187
+ collar.position.set(0, 0.78, 0); collar.castShadow = true;
188
+ outfitGroup.add(collar);
189
+ // Sleeves
190
+ [-0.22, 0.22].forEach(function(sx) {
191
+ var sleeve = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.14, 0.1), jacketMat);
192
+ sleeve.position.set(sx, 0.65, 0); sleeve.castShadow = true;
193
+ outfitGroup.add(sleeve);
194
+ });
195
+ break;
196
+ }
197
+ }
198
+
199
+ charGroup.add(outfitGroup);
200
+ return outfitGroup;
201
+ }
202
+
203
+ export function removeOutfit(charGroup) {
204
+ var toRemove = [];
205
+ charGroup.children.forEach(function(c) {
206
+ if (c.userData && c.userData.isOutfit) toRemove.push(c);
207
+ });
208
+ toRemove.forEach(function(c) {
209
+ charGroup.remove(c);
210
+ c.traverse(function(ch) { if (ch.geometry) ch.geometry.dispose(); if (ch.material) ch.material.dispose(); });
211
+ });
212
+ }
@@ -0,0 +1,75 @@
1
+ import * as THREE from 'three';
2
+ import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js';
3
+ import { SpectatorCamera } from './spectator-camera.js';
4
+ import { S } from './state.js';
5
+
6
+ export function initScene() {
7
+ S.container = document.getElementById('office-3d-container');
8
+ if (!S.container) return false;
9
+
10
+ S.scene = new THREE.Scene();
11
+ S.scene.background = new THREE.Color(0x0d1117);
12
+ S.scene.fog = new THREE.Fog(0x0d1117, 25, 55);
13
+
14
+ S.camera = new THREE.PerspectiveCamera(50, S.container.clientWidth / S.container.clientHeight, 0.1, 200);
15
+ S.camera.position.set(0, 12, 16);
16
+ S.camera.lookAt(0, 0, 0);
17
+
18
+ S.renderer = new THREE.WebGLRenderer({ antialias: true });
19
+ S.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
20
+ S.renderer.setSize(S.container.clientWidth, S.container.clientHeight);
21
+ S.renderer.shadowMap.enabled = true;
22
+ S.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
23
+ S.renderer.toneMapping = THREE.ACESFilmicToneMapping;
24
+ S.renderer.toneMappingExposure = 1.2;
25
+ S.container.appendChild(S.renderer.domElement);
26
+
27
+ S.cssRenderer = new CSS2DRenderer();
28
+ S.cssRenderer.setSize(S.container.clientWidth, S.container.clientHeight);
29
+ S.cssRenderer.domElement.style.position = 'absolute';
30
+ S.cssRenderer.domElement.style.top = '0';
31
+ S.cssRenderer.domElement.style.left = '0';
32
+ S.cssRenderer.domElement.style.pointerEvents = 'none';
33
+ S.container.appendChild(S.cssRenderer.domElement);
34
+
35
+ // Spectator camera — free movement, no limits
36
+ S.controls = new SpectatorCamera(S.camera, S.renderer.domElement);
37
+
38
+ // Lighting
39
+ var ambient = new THREE.AmbientLight(0xffffff, 0.5);
40
+ S.scene.add(ambient);
41
+
42
+ var dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
43
+ dirLight.position.set(8, 12, 8);
44
+ dirLight.castShadow = true;
45
+ dirLight.shadow.mapSize.width = 2048;
46
+ dirLight.shadow.mapSize.height = 2048;
47
+ dirLight.shadow.camera.left = -15;
48
+ dirLight.shadow.camera.right = 15;
49
+ dirLight.shadow.camera.top = 15;
50
+ dirLight.shadow.camera.bottom = -15;
51
+ dirLight.shadow.camera.near = 0.5;
52
+ dirLight.shadow.camera.far = 30;
53
+ dirLight.shadow.bias = -0.001;
54
+ S.scene.add(dirLight);
55
+
56
+ var fillLight = new THREE.DirectionalLight(0x6c8aff, 0.15);
57
+ fillLight.position.set(-5, 8, -5);
58
+ S.scene.add(fillLight);
59
+
60
+ S.clock = new THREE.Clock();
61
+
62
+ S.resizeObserver = new ResizeObserver(function() {
63
+ if (!S.container || !S.running) return;
64
+ var w = S.container.clientWidth;
65
+ var h = S.container.clientHeight;
66
+ if (w <= 0 || h <= 0) return;
67
+ S.camera.aspect = w / h;
68
+ S.camera.updateProjectionMatrix();
69
+ S.renderer.setSize(w, h);
70
+ S.cssRenderer.setSize(w, h);
71
+ });
72
+ S.resizeObserver.observe(S.container);
73
+
74
+ return true;
75
+ }
@@ -0,0 +1,177 @@
1
+ import * as THREE from 'three';
2
+
3
+ // Spectator / fly camera — Unreal Engine style free movement
4
+ // Left-drag: look around (rotate)
5
+ // Right-drag: pan (strafe)
6
+ // WASD: move forward/back/left/right
7
+ // Q/E: down/up
8
+ // Scroll: dolly forward/back
9
+ // Shift: fast move
10
+ // Middle-drag: also pan
11
+
12
+ export function SpectatorCamera(camera, domElement) {
13
+ var self = this;
14
+ self.camera = camera;
15
+ self.domElement = domElement;
16
+ self.enabled = true;
17
+
18
+ // Movement
19
+ self.moveSpeed = 4;
20
+ self.fastMultiplier = 3;
21
+ self.scrollSpeed = 2;
22
+ self.lookSpeed = 0.003;
23
+ self.panSpeed = 0.01;
24
+ self.damping = 0.88;
25
+
26
+ // Internal state
27
+ var euler = new THREE.Euler(0, 0, 0, 'YXZ');
28
+ var velocity = new THREE.Vector3();
29
+ var moveDir = new THREE.Vector3();
30
+ var keys = {};
31
+ var isLeftDrag = false;
32
+ var isRightDrag = false;
33
+ var isMiddleDrag = false;
34
+ var lastMouse = { x: 0, y: 0 };
35
+
36
+ // Initialize euler from camera
37
+ euler.setFromQuaternion(camera.quaternion, 'YXZ');
38
+
39
+ // --- Event handlers ---
40
+ function onMouseDown(e) {
41
+ if (!self.enabled) return;
42
+ // Only capture if click is on the 3D canvas area
43
+ if (e.target !== domElement) return;
44
+ // Take focus away from any text input so WASD works
45
+ if (document.activeElement && document.activeElement !== document.body) {
46
+ document.activeElement.blur();
47
+ }
48
+ if (e.button === 0) isLeftDrag = true;
49
+ if (e.button === 2) isRightDrag = true;
50
+ if (e.button === 1) isMiddleDrag = true;
51
+ lastMouse.x = e.clientX;
52
+ lastMouse.y = e.clientY;
53
+ e.preventDefault();
54
+ }
55
+
56
+ function onMouseUp(e) {
57
+ if (e.button === 0) isLeftDrag = false;
58
+ if (e.button === 2) isRightDrag = false;
59
+ if (e.button === 1) isMiddleDrag = false;
60
+ }
61
+
62
+ function onMouseMove(e) {
63
+ if (!self.enabled) return;
64
+ var dx = e.clientX - lastMouse.x;
65
+ var dy = e.clientY - lastMouse.y;
66
+ lastMouse.x = e.clientX;
67
+ lastMouse.y = e.clientY;
68
+
69
+ if (isRightDrag) {
70
+ // Look around (rotate)
71
+ euler.y -= dx * self.lookSpeed;
72
+ euler.x -= dy * self.lookSpeed;
73
+ euler.x = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, euler.x));
74
+ camera.quaternion.setFromEuler(euler);
75
+ }
76
+
77
+ if (isLeftDrag || isMiddleDrag) {
78
+ // Pan (strafe)
79
+ var right = new THREE.Vector3();
80
+ var up = new THREE.Vector3();
81
+ camera.getWorldDirection(new THREE.Vector3());
82
+ right.setFromMatrixColumn(camera.matrixWorld, 0);
83
+ up.setFromMatrixColumn(camera.matrixWorld, 1);
84
+ camera.position.addScaledVector(right, -dx * self.panSpeed);
85
+ camera.position.addScaledVector(up, dy * self.panSpeed);
86
+ }
87
+ }
88
+
89
+ function onWheel(e) {
90
+ if (!self.enabled) return;
91
+ // Check if scroll is over the 3D container
92
+ var rect = domElement.getBoundingClientRect();
93
+ if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) return;
94
+ var forward = new THREE.Vector3();
95
+ camera.getWorldDirection(forward);
96
+ var amount = -e.deltaY * 0.01 * self.scrollSpeed;
97
+ camera.position.addScaledVector(forward, amount);
98
+ e.preventDefault();
99
+ }
100
+
101
+ function isTyping() {
102
+ var el = document.activeElement;
103
+ if (!el) return false;
104
+ var tag = el.tagName;
105
+ return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
106
+ }
107
+
108
+ function onKeyDown(e) {
109
+ if (!self.enabled || isTyping()) return;
110
+ keys[e.code] = true;
111
+ }
112
+
113
+ function onKeyUp(e) {
114
+ keys[e.code] = false;
115
+ }
116
+
117
+ function onContextMenu(e) {
118
+ if (e.target === domElement) e.preventDefault();
119
+ }
120
+
121
+ // --- Public methods ---
122
+ self.update = function(dt) {
123
+ if (!self.enabled) return;
124
+
125
+ var speed = self.moveSpeed * (keys['ShiftLeft'] || keys['ShiftRight'] ? self.fastMultiplier : 1);
126
+
127
+ // Build movement direction in camera space
128
+ moveDir.set(0, 0, 0);
129
+ if (keys['KeyW'] || keys['ArrowUp']) moveDir.z -= 1;
130
+ if (keys['KeyS'] || keys['ArrowDown']) moveDir.z += 1;
131
+ if (keys['KeyA'] || keys['ArrowLeft']) moveDir.x -= 1;
132
+ if (keys['KeyD'] || keys['ArrowRight']) moveDir.x += 1;
133
+ if (keys['KeyE'] || keys['Space']) moveDir.y += 1;
134
+ if (keys['KeyQ'] || keys['ControlLeft']) moveDir.y -= 1;
135
+
136
+ if (moveDir.lengthSq() > 0) {
137
+ moveDir.normalize();
138
+ // Transform to world space using camera orientation
139
+ var forward = new THREE.Vector3();
140
+ var right = new THREE.Vector3();
141
+ camera.getWorldDirection(forward);
142
+ right.crossVectors(forward, camera.up).normalize();
143
+ var worldUp = new THREE.Vector3(0, 1, 0);
144
+
145
+ // Forward/back along camera look direction (projected on XZ for ground feel, or full 3D)
146
+ velocity.addScaledVector(forward, -moveDir.z * speed * dt);
147
+ velocity.addScaledVector(right, moveDir.x * speed * dt);
148
+ velocity.addScaledVector(worldUp, moveDir.y * speed * dt);
149
+ }
150
+
151
+ // Apply velocity with damping
152
+ camera.position.add(velocity);
153
+ velocity.multiplyScalar(self.damping);
154
+
155
+ // Kill tiny velocities
156
+ if (velocity.lengthSq() < 0.0001) velocity.set(0, 0, 0);
157
+ };
158
+
159
+ self.dispose = function() {
160
+ domElement.removeEventListener('mousedown', onMouseDown);
161
+ document.removeEventListener('mouseup', onMouseUp);
162
+ document.removeEventListener('mousemove', onMouseMove);
163
+ domElement.removeEventListener('wheel', onWheel);
164
+ document.removeEventListener('keydown', onKeyDown);
165
+ document.removeEventListener('keyup', onKeyUp);
166
+ domElement.removeEventListener('contextmenu', onContextMenu);
167
+ };
168
+
169
+ // --- Bind events ---
170
+ domElement.addEventListener('mousedown', onMouseDown);
171
+ document.addEventListener('mouseup', onMouseUp);
172
+ document.addEventListener('mousemove', onMouseMove);
173
+ domElement.addEventListener('wheel', onWheel, { passive: false });
174
+ document.addEventListener('keydown', onKeyDown);
175
+ document.addEventListener('keyup', onKeyUp);
176
+ domElement.addEventListener('contextmenu', onContextMenu);
177
+ }
@@ -0,0 +1,25 @@
1
+ // Shared mutable state for the 3D office system.
2
+ // All modules import S and read/write through it.
3
+ export const S = {
4
+ scene: null,
5
+ camera: null,
6
+ renderer: null,
7
+ cssRenderer: null,
8
+ controls: null,
9
+ container: null,
10
+ running: false,
11
+ animationId: null,
12
+ clock: null,
13
+ agents3d: {},
14
+ lastProcessedMsg: 0,
15
+ currentEnv: 'modern',
16
+ furnitureGroup: null,
17
+ deskMeshes: [],
18
+ syncInterval: null,
19
+ resizeObserver: null,
20
+ cachedTasks: [],
21
+ conversationVelocity: 0,
22
+ monitorCanvases: {},
23
+ fpsCounter: 0,
24
+ fpsTime: 0,
25
+ };
package/package.json CHANGED
@@ -1,56 +1,58 @@
1
- {
2
- "name": "let-them-talk",
3
- "version": "3.5.1",
4
- "description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
5
- "main": "server.js",
6
- "bin": {
7
- "agent-bridge": "./cli.js",
8
- "let-them-talk": "./cli.js"
9
- },
10
- "scripts": {
11
- "start": "node server.js",
12
- "dashboard": "node dashboard.js",
13
- "test": "echo \"No tests yet\""
14
- },
15
- "engines": {
16
- "node": ">=16.0.0"
17
- },
18
- "files": [
19
- "server.js",
20
- "dashboard.js",
21
- "dashboard.html",
22
- "cli.js",
23
- "templates/",
24
- "conversation-templates/",
25
- "logo.png",
26
- "LICENSE",
27
- "SECURITY.md",
28
- "CHANGELOG.md"
29
- ],
30
- "keywords": [
31
- "mcp",
32
- "claude",
33
- "claude-code",
34
- "gemini-cli",
35
- "codex-cli",
36
- "agent",
37
- "multi-agent",
38
- "communication",
39
- "message-broker",
40
- "ai-agents",
41
- "let-them-talk"
42
- ],
43
- "repository": {
44
- "type": "git",
45
- "url": "git+https://github.com/Dekelelz/let-them-talk.git"
46
- },
47
- "homepage": "https://talk.unrealai.studio",
48
- "bugs": {
49
- "url": "https://github.com/Dekelelz/let-them-talk/issues"
50
- },
51
- "author": "Dekelelz <contact@talk.unrealai.studio>",
52
- "license": "SEE LICENSE IN LICENSE",
53
- "dependencies": {
54
- "@modelcontextprotocol/sdk": "1.27.1"
55
- }
56
- }
1
+ {
2
+ "name": "let-them-talk",
3
+ "version": "3.6.0",
4
+ "description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
5
+ "main": "server.js",
6
+ "bin": {
7
+ "agent-bridge": "./cli.js",
8
+ "let-them-talk": "./cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node server.js",
12
+ "dashboard": "node dashboard.js",
13
+ "test": "echo \"No tests yet\""
14
+ },
15
+ "engines": {
16
+ "node": ">=16.0.0"
17
+ },
18
+ "files": [
19
+ "server.js",
20
+ "dashboard.js",
21
+ "dashboard.html",
22
+ "office/",
23
+ "mods/",
24
+ "cli.js",
25
+ "templates/",
26
+ "conversation-templates/",
27
+ "logo.png",
28
+ "LICENSE",
29
+ "SECURITY.md",
30
+ "CHANGELOG.md"
31
+ ],
32
+ "keywords": [
33
+ "mcp",
34
+ "claude",
35
+ "claude-code",
36
+ "gemini-cli",
37
+ "codex-cli",
38
+ "agent",
39
+ "multi-agent",
40
+ "communication",
41
+ "message-broker",
42
+ "ai-agents",
43
+ "let-them-talk"
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/Dekelelz/let-them-talk.git"
48
+ },
49
+ "homepage": "https://talk.unrealai.studio",
50
+ "bugs": {
51
+ "url": "https://github.com/Dekelelz/let-them-talk/issues"
52
+ },
53
+ "author": "Dekelelz <contact@talk.unrealai.studio>",
54
+ "license": "SEE LICENSE IN LICENSE",
55
+ "dependencies": {
56
+ "@modelcontextprotocol/sdk": "1.27.1"
57
+ }
58
+ }