watercooler 0.0.5 → 0.0.6
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/package.json +1 -1
- package/public/app.js +759 -260
- package/public/index.html +7 -7
package/public/app.js
CHANGED
|
@@ -4,7 +4,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
4
4
|
// State
|
|
5
5
|
let config = { user: '', mailbox: '', avatar: null };
|
|
6
6
|
let messages = []; // Messages TO user (for main panel)
|
|
7
|
-
let allMessages = []; // All messages involving user (for
|
|
7
|
+
let allMessages = []; // All messages involving user (for desk dialogs)
|
|
8
8
|
let recipients = [];
|
|
9
9
|
let avatarStates = {}; // Map of name -> {tool_name, timestamp}
|
|
10
10
|
let scene, camera, renderer, controls;
|
|
@@ -12,10 +12,10 @@ let agentMeshes = new Map();
|
|
|
12
12
|
let connectionLines = [];
|
|
13
13
|
let raycaster, mouse;
|
|
14
14
|
|
|
15
|
-
// Color palette for agents
|
|
15
|
+
// Color palette for agents - modern muted tones
|
|
16
16
|
const agentColors = [
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
0x5EEAD4, 0x6EE7B7, 0x7DD3FC, 0xA78BFA, 0xFBBF24,
|
|
18
|
+
0xF9A8D4, 0x86EFAC, 0x93C5FD, 0xC4B5FD, 0x67E8F9
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
function getAgentColor(name) {
|
|
@@ -26,81 +26,107 @@ function getAgentColor(name) {
|
|
|
26
26
|
return agentColors[Math.abs(hash) % agentColors.length];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Platform dimensions
|
|
30
|
+
const PLATFORM_SIZE = 60;
|
|
31
|
+
const PLATFORM_HEIGHT = 2;
|
|
32
|
+
const WALL_HEIGHT = 18;
|
|
33
|
+
|
|
34
|
+
// Animated objects
|
|
35
|
+
let holoSphere = null;
|
|
36
|
+
let holoParticles = null;
|
|
37
|
+
let glowLights = [];
|
|
38
|
+
let floatingParticles = [];
|
|
39
|
+
|
|
29
40
|
// Initialize Three.js
|
|
30
41
|
function init() {
|
|
31
42
|
const container = document.getElementById('canvas-container');
|
|
32
43
|
|
|
33
44
|
scene = new THREE.Scene();
|
|
34
|
-
scene.background = new THREE.Color(
|
|
45
|
+
scene.background = new THREE.Color(0x1a3a3a);
|
|
46
|
+
scene.fog = new THREE.FogExp2(0x1a3a3a, 0.003);
|
|
35
47
|
|
|
36
|
-
camera = new THREE.PerspectiveCamera(
|
|
37
|
-
camera.position.set(
|
|
48
|
+
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
49
|
+
camera.position.set(55, 45, 55);
|
|
38
50
|
|
|
39
51
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
40
52
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
53
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
41
54
|
renderer.shadowMap.enabled = true;
|
|
42
55
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
56
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
57
|
+
renderer.toneMappingExposure = 1.2;
|
|
43
58
|
container.appendChild(renderer.domElement);
|
|
44
59
|
|
|
45
60
|
controls = new OrbitControls(camera, renderer.domElement);
|
|
46
61
|
controls.enableDamping = true;
|
|
47
62
|
controls.dampingFactor = 0.05;
|
|
48
|
-
controls.maxPolarAngle = Math.PI / 2 - 0.
|
|
49
|
-
controls.minDistance =
|
|
50
|
-
controls.maxDistance =
|
|
63
|
+
controls.maxPolarAngle = Math.PI / 2 - 0.05;
|
|
64
|
+
controls.minDistance = 25;
|
|
65
|
+
controls.maxDistance = 120;
|
|
51
66
|
controls.enableZoom = true;
|
|
52
67
|
controls.zoomSpeed = 0.8;
|
|
53
|
-
controls.enablePan = false;
|
|
68
|
+
controls.enablePan = false;
|
|
69
|
+
controls.target.set(0, 5, 0);
|
|
54
70
|
controls.touches = {
|
|
55
71
|
ONE: THREE.TOUCH.ROTATE,
|
|
56
72
|
TWO: THREE.TOUCH.DOLLY_PAN
|
|
57
73
|
};
|
|
58
74
|
|
|
59
|
-
// Lighting
|
|
60
|
-
|
|
75
|
+
// === Lighting ===
|
|
76
|
+
// Soft ambient
|
|
77
|
+
const ambientLight = new THREE.AmbientLight(0x2d5a5a, 0.8);
|
|
61
78
|
scene.add(ambientLight);
|
|
62
79
|
|
|
63
|
-
|
|
64
|
-
dirLight.
|
|
80
|
+
// Main directional light (warm)
|
|
81
|
+
const dirLight = new THREE.DirectionalLight(0xfff5e6, 0.6);
|
|
82
|
+
dirLight.position.set(40, 80, 30);
|
|
65
83
|
dirLight.castShadow = true;
|
|
66
|
-
dirLight.shadow.camera.left = -
|
|
67
|
-
dirLight.shadow.camera.right =
|
|
68
|
-
dirLight.shadow.camera.top =
|
|
69
|
-
dirLight.shadow.camera.bottom = -
|
|
84
|
+
dirLight.shadow.camera.left = -40;
|
|
85
|
+
dirLight.shadow.camera.right = 40;
|
|
86
|
+
dirLight.shadow.camera.top = 40;
|
|
87
|
+
dirLight.shadow.camera.bottom = -40;
|
|
70
88
|
dirLight.shadow.mapSize.width = 2048;
|
|
71
89
|
dirLight.shadow.mapSize.height = 2048;
|
|
90
|
+
dirLight.shadow.bias = -0.001;
|
|
72
91
|
scene.add(dirLight);
|
|
73
92
|
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
ground.receiveShadow = true;
|
|
83
|
-
scene.add(ground);
|
|
93
|
+
// Fill light from below (teal tint)
|
|
94
|
+
const fillLight = new THREE.DirectionalLight(0x4fd1c5, 0.3);
|
|
95
|
+
fillLight.position.set(-20, 5, -20);
|
|
96
|
+
scene.add(fillLight);
|
|
97
|
+
|
|
98
|
+
// Hemisphere light for natural ambient
|
|
99
|
+
const hemiLight = new THREE.HemisphereLight(0x4fd1c5, 0x1a3a3a, 0.4);
|
|
100
|
+
scene.add(hemiLight);
|
|
84
101
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
grid.material.opacity = 0.2;
|
|
88
|
-
grid.material.transparent = true;
|
|
89
|
-
scene.add(grid);
|
|
102
|
+
// === Platform ===
|
|
103
|
+
createPlatform();
|
|
90
104
|
|
|
91
|
-
//
|
|
92
|
-
|
|
105
|
+
// === Glass Walls ===
|
|
106
|
+
createGlassWalls();
|
|
107
|
+
|
|
108
|
+
// === Decorative Plants ===
|
|
109
|
+
createPlants();
|
|
110
|
+
|
|
111
|
+
// === Holographic Sphere ===
|
|
112
|
+
createHolographicSphere();
|
|
113
|
+
|
|
114
|
+
// === Ambient Glow Lights ===
|
|
115
|
+
createGlowLights();
|
|
116
|
+
|
|
117
|
+
// === Floating Particles ===
|
|
118
|
+
createFloatingParticles();
|
|
93
119
|
|
|
94
120
|
window.addEventListener('resize', onWindowResize);
|
|
95
121
|
|
|
96
|
-
// Raycaster for
|
|
122
|
+
// Raycaster for desk clicks
|
|
97
123
|
raycaster = new THREE.Raycaster();
|
|
98
124
|
mouse = new THREE.Vector2();
|
|
99
|
-
renderer.domElement.addEventListener('click',
|
|
125
|
+
renderer.domElement.addEventListener('click', onDeskClick);
|
|
100
126
|
|
|
101
127
|
// Add touch support for mobile
|
|
102
|
-
renderer.domElement.addEventListener('touchstart',
|
|
103
|
-
renderer.domElement.addEventListener('touchend',
|
|
128
|
+
renderer.domElement.addEventListener('touchstart', onDeskTouchStart, { passive: false });
|
|
129
|
+
renderer.domElement.addEventListener('touchend', onDeskTouchEnd, { passive: false });
|
|
104
130
|
|
|
105
131
|
// Disable context menu on mobile for better UX
|
|
106
132
|
renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
@@ -113,102 +139,551 @@ function init() {
|
|
|
113
139
|
animate();
|
|
114
140
|
}
|
|
115
141
|
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
142
|
+
function createPlatform() {
|
|
143
|
+
// Main platform - dark concrete slab
|
|
144
|
+
const platformGeo = new THREE.BoxGeometry(PLATFORM_SIZE, PLATFORM_HEIGHT, PLATFORM_SIZE);
|
|
145
|
+
const platformMat = new THREE.MeshStandardMaterial({
|
|
146
|
+
color: 0x3a3a3a,
|
|
147
|
+
roughness: 0.4,
|
|
148
|
+
metalness: 0.1
|
|
149
|
+
});
|
|
150
|
+
const platform = new THREE.Mesh(platformGeo, platformMat);
|
|
151
|
+
platform.position.y = PLATFORM_HEIGHT / 2;
|
|
152
|
+
platform.receiveShadow = true;
|
|
153
|
+
platform.castShadow = true;
|
|
154
|
+
scene.add(platform);
|
|
155
|
+
|
|
156
|
+
// Edge trim - lighter accent
|
|
157
|
+
const trimGeo = new THREE.BoxGeometry(PLATFORM_SIZE + 0.5, 0.3, PLATFORM_SIZE + 0.5);
|
|
158
|
+
const trimMat = new THREE.MeshStandardMaterial({
|
|
159
|
+
color: 0x5a5a5a,
|
|
160
|
+
roughness: 0.3,
|
|
161
|
+
metalness: 0.3
|
|
162
|
+
});
|
|
163
|
+
const trim = new THREE.Mesh(trimGeo, trimMat);
|
|
164
|
+
trim.position.y = PLATFORM_HEIGHT + 0.15;
|
|
165
|
+
scene.add(trim);
|
|
166
|
+
|
|
167
|
+
// Floor surface - polished concrete with subtle grid
|
|
168
|
+
const floorGeo = new THREE.PlaneGeometry(PLATFORM_SIZE - 2, PLATFORM_SIZE - 2);
|
|
169
|
+
const floorMat = new THREE.MeshStandardMaterial({
|
|
170
|
+
color: 0x4a4a4a,
|
|
171
|
+
roughness: 0.2,
|
|
172
|
+
metalness: 0.15
|
|
173
|
+
});
|
|
174
|
+
const floor = new THREE.Mesh(floorGeo, floorMat);
|
|
175
|
+
floor.rotation.x = -Math.PI / 2;
|
|
176
|
+
floor.position.y = PLATFORM_HEIGHT + 0.02;
|
|
177
|
+
floor.receiveShadow = true;
|
|
178
|
+
scene.add(floor);
|
|
179
|
+
|
|
180
|
+
// Subtle grid on floor
|
|
181
|
+
const gridHelper = new THREE.GridHelper(PLATFORM_SIZE - 4, 20, 0x555555, 0x444444);
|
|
182
|
+
gridHelper.position.y = PLATFORM_HEIGHT + 0.05;
|
|
183
|
+
gridHelper.material.opacity = 0.15;
|
|
184
|
+
gridHelper.material.transparent = true;
|
|
185
|
+
scene.add(gridHelper);
|
|
186
|
+
|
|
187
|
+
// Ground below platform (dark reflection surface)
|
|
188
|
+
const groundGeo = new THREE.PlaneGeometry(300, 300);
|
|
189
|
+
const groundMat = new THREE.MeshStandardMaterial({
|
|
190
|
+
color: 0x1a3a3a,
|
|
191
|
+
roughness: 0.6,
|
|
192
|
+
metalness: 0.2
|
|
193
|
+
});
|
|
194
|
+
const ground = new THREE.Mesh(groundGeo, groundMat);
|
|
195
|
+
ground.rotation.x = -Math.PI / 2;
|
|
196
|
+
ground.position.y = -0.1;
|
|
197
|
+
ground.receiveShadow = true;
|
|
198
|
+
scene.add(ground);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createGlassWalls() {
|
|
202
|
+
const glassMat = new THREE.MeshPhysicalMaterial({
|
|
203
|
+
color: 0x88cccc,
|
|
204
|
+
transparent: true,
|
|
205
|
+
opacity: 0.08,
|
|
206
|
+
roughness: 0.05,
|
|
207
|
+
metalness: 0.0,
|
|
208
|
+
transmission: 0.95,
|
|
209
|
+
thickness: 0.5,
|
|
210
|
+
side: THREE.DoubleSide
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const wallHeight = WALL_HEIGHT;
|
|
214
|
+
const wallY = PLATFORM_HEIGHT + wallHeight / 2;
|
|
215
|
+
const halfSize = PLATFORM_SIZE / 2;
|
|
216
|
+
|
|
217
|
+
// Back wall
|
|
218
|
+
const backWall = new THREE.Mesh(
|
|
219
|
+
new THREE.PlaneGeometry(PLATFORM_SIZE, wallHeight),
|
|
220
|
+
glassMat
|
|
221
|
+
);
|
|
222
|
+
backWall.position.set(0, wallY, -halfSize);
|
|
223
|
+
scene.add(backWall);
|
|
224
|
+
|
|
225
|
+
// Left wall
|
|
226
|
+
const leftWall = new THREE.Mesh(
|
|
227
|
+
new THREE.PlaneGeometry(PLATFORM_SIZE, wallHeight),
|
|
228
|
+
glassMat
|
|
229
|
+
);
|
|
230
|
+
leftWall.position.set(-halfSize, wallY, 0);
|
|
231
|
+
leftWall.rotation.y = Math.PI / 2;
|
|
232
|
+
scene.add(leftWall);
|
|
233
|
+
|
|
234
|
+
// Right wall (partial, for openness)
|
|
235
|
+
const rightWall = new THREE.Mesh(
|
|
236
|
+
new THREE.PlaneGeometry(PLATFORM_SIZE, wallHeight),
|
|
237
|
+
glassMat
|
|
238
|
+
);
|
|
239
|
+
rightWall.position.set(halfSize, wallY, 0);
|
|
240
|
+
rightWall.rotation.y = -Math.PI / 2;
|
|
241
|
+
scene.add(rightWall);
|
|
242
|
+
|
|
243
|
+
// Glass edge frames (vertical pillars at corners)
|
|
244
|
+
const pillarGeo = new THREE.BoxGeometry(0.5, wallHeight, 0.5);
|
|
245
|
+
const pillarMat = new THREE.MeshStandardMaterial({
|
|
246
|
+
color: 0x777777,
|
|
247
|
+
roughness: 0.2,
|
|
248
|
+
metalness: 0.6
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const corners = [
|
|
252
|
+
[-halfSize, wallY, -halfSize],
|
|
253
|
+
[halfSize, wallY, -halfSize],
|
|
254
|
+
[-halfSize, wallY, halfSize],
|
|
255
|
+
[halfSize, wallY, halfSize]
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
corners.forEach(pos => {
|
|
259
|
+
const pillar = new THREE.Mesh(pillarGeo, pillarMat);
|
|
260
|
+
pillar.position.set(...pos);
|
|
261
|
+
pillar.castShadow = true;
|
|
262
|
+
scene.add(pillar);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Top edge frame
|
|
266
|
+
const topFrameMat = new THREE.MeshStandardMaterial({
|
|
267
|
+
color: 0x666666,
|
|
268
|
+
roughness: 0.2,
|
|
269
|
+
metalness: 0.5
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const frameY = PLATFORM_HEIGHT + wallHeight;
|
|
273
|
+
|
|
274
|
+
// Back top frame
|
|
275
|
+
const backFrame = new THREE.Mesh(new THREE.BoxGeometry(PLATFORM_SIZE, 0.3, 0.3), topFrameMat);
|
|
276
|
+
backFrame.position.set(0, frameY, -halfSize);
|
|
277
|
+
scene.add(backFrame);
|
|
278
|
+
|
|
279
|
+
// Left top frame
|
|
280
|
+
const leftFrame = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, PLATFORM_SIZE), topFrameMat);
|
|
281
|
+
leftFrame.position.set(-halfSize, frameY, 0);
|
|
282
|
+
scene.add(leftFrame);
|
|
283
|
+
|
|
284
|
+
// Right top frame
|
|
285
|
+
const rightFrame = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, PLATFORM_SIZE), topFrameMat);
|
|
286
|
+
rightFrame.position.set(halfSize, frameY, 0);
|
|
287
|
+
scene.add(rightFrame);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function createPlants() {
|
|
291
|
+
const plantPositions = [
|
|
292
|
+
// Corner clusters
|
|
293
|
+
[-25, PLATFORM_HEIGHT, -25],
|
|
294
|
+
[25, PLATFORM_HEIGHT, -25],
|
|
295
|
+
[-25, PLATFORM_HEIGHT, 25],
|
|
296
|
+
[25, PLATFORM_HEIGHT, 25],
|
|
297
|
+
// Edge accents
|
|
298
|
+
[-20, PLATFORM_HEIGHT, 27],
|
|
299
|
+
[20, PLATFORM_HEIGHT, 27],
|
|
300
|
+
[-27, PLATFORM_HEIGHT, 0],
|
|
301
|
+
[27, PLATFORM_HEIGHT, -15],
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
plantPositions.forEach(pos => {
|
|
305
|
+
createPlantCluster(pos[0], pos[1], pos[2]);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function createPlantCluster(x, y, z) {
|
|
310
|
+
const group = new THREE.Group();
|
|
311
|
+
|
|
312
|
+
// Planter box
|
|
313
|
+
const planterGeo = new THREE.BoxGeometry(3, 1.5, 3);
|
|
314
|
+
const planterMat = new THREE.MeshStandardMaterial({
|
|
315
|
+
color: 0x2a2a2a,
|
|
316
|
+
roughness: 0.6,
|
|
317
|
+
metalness: 0.1
|
|
318
|
+
});
|
|
319
|
+
const planter = new THREE.Mesh(planterGeo, planterMat);
|
|
320
|
+
planter.position.y = 0.75;
|
|
321
|
+
planter.castShadow = true;
|
|
322
|
+
planter.receiveShadow = true;
|
|
323
|
+
group.add(planter);
|
|
324
|
+
|
|
325
|
+
// Soil
|
|
326
|
+
const soilGeo = new THREE.BoxGeometry(2.6, 0.2, 2.6);
|
|
327
|
+
const soilMat = new THREE.MeshStandardMaterial({ color: 0x3d2817 });
|
|
328
|
+
const soil = new THREE.Mesh(soilGeo, soilMat);
|
|
329
|
+
soil.position.y = 1.5;
|
|
330
|
+
group.add(soil);
|
|
331
|
+
|
|
332
|
+
// Foliage - multiple spheres for bush look
|
|
333
|
+
const leafColors = [0x1a6b3a, 0x228B22, 0x2d8b4e, 0x1f7a3f];
|
|
334
|
+
|
|
335
|
+
for (let i = 0; i < 5; i++) {
|
|
336
|
+
const size = 0.6 + Math.random() * 0.8;
|
|
337
|
+
const leafGeo = new THREE.SphereGeometry(size, 8, 8);
|
|
338
|
+
const leafMat = new THREE.MeshStandardMaterial({
|
|
339
|
+
color: leafColors[Math.floor(Math.random() * leafColors.length)],
|
|
340
|
+
roughness: 0.8
|
|
341
|
+
});
|
|
342
|
+
const leaf = new THREE.Mesh(leafGeo, leafMat);
|
|
343
|
+
leaf.position.set(
|
|
344
|
+
(Math.random() - 0.5) * 1.5,
|
|
345
|
+
1.8 + Math.random() * 1.5,
|
|
346
|
+
(Math.random() - 0.5) * 1.5
|
|
347
|
+
);
|
|
348
|
+
leaf.castShadow = true;
|
|
349
|
+
group.add(leaf);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Tall fern-like elements (cone shapes)
|
|
353
|
+
for (let i = 0; i < 3; i++) {
|
|
354
|
+
const fernGeo = new THREE.ConeGeometry(0.3, 2 + Math.random() * 2, 6);
|
|
355
|
+
const fernMat = new THREE.MeshStandardMaterial({
|
|
356
|
+
color: 0x1a5c2e,
|
|
357
|
+
roughness: 0.7
|
|
358
|
+
});
|
|
359
|
+
const fern = new THREE.Mesh(fernGeo, fernMat);
|
|
360
|
+
fern.position.set(
|
|
361
|
+
(Math.random() - 0.5) * 1.5,
|
|
362
|
+
2.5 + Math.random() * 1.5,
|
|
363
|
+
(Math.random() - 0.5) * 1.5
|
|
364
|
+
);
|
|
365
|
+
fern.castShadow = true;
|
|
366
|
+
group.add(fern);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
group.position.set(x, y, z);
|
|
370
|
+
scene.add(group);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function createHolographicSphere() {
|
|
374
|
+
// Wireframe sphere
|
|
375
|
+
const sphereGeo = new THREE.IcosahedronGeometry(6, 3);
|
|
376
|
+
const sphereMat = new THREE.MeshBasicMaterial({
|
|
377
|
+
color: 0x4fd1c5,
|
|
378
|
+
wireframe: true,
|
|
379
|
+
transparent: true,
|
|
380
|
+
opacity: 0.3
|
|
381
|
+
});
|
|
382
|
+
holoSphere = new THREE.Mesh(sphereGeo, sphereMat);
|
|
383
|
+
holoSphere.position.set(0, PLATFORM_HEIGHT + 12, 0);
|
|
384
|
+
scene.add(holoSphere);
|
|
385
|
+
|
|
386
|
+
// Inner glow sphere
|
|
387
|
+
const innerGeo = new THREE.SphereGeometry(4, 32, 32);
|
|
388
|
+
const innerMat = new THREE.MeshBasicMaterial({
|
|
389
|
+
color: 0x4fd1c5,
|
|
390
|
+
transparent: true,
|
|
391
|
+
opacity: 0.05
|
|
392
|
+
});
|
|
393
|
+
const innerSphere = new THREE.Mesh(innerGeo, innerMat);
|
|
394
|
+
holoSphere.add(innerSphere);
|
|
395
|
+
|
|
396
|
+
// Point cloud on sphere surface
|
|
397
|
+
const particleCount = 300;
|
|
398
|
+
const positions = new Float32Array(particleCount * 3);
|
|
399
|
+
for (let i = 0; i < particleCount; i++) {
|
|
400
|
+
const theta = Math.random() * Math.PI * 2;
|
|
401
|
+
const phi = Math.acos(2 * Math.random() - 1);
|
|
402
|
+
const r = 5.5 + Math.random() * 0.5;
|
|
403
|
+
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
|
|
404
|
+
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
|
|
405
|
+
positions[i * 3 + 2] = r * Math.cos(phi);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const particleGeo = new THREE.BufferGeometry();
|
|
409
|
+
particleGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
410
|
+
const particleMat = new THREE.PointsMaterial({
|
|
411
|
+
color: 0x88ffee,
|
|
412
|
+
size: 0.15,
|
|
413
|
+
transparent: true,
|
|
414
|
+
opacity: 0.6,
|
|
415
|
+
blending: THREE.AdditiveBlending
|
|
416
|
+
});
|
|
417
|
+
holoParticles = new THREE.Points(particleGeo, particleMat);
|
|
418
|
+
holoSphere.add(holoParticles);
|
|
419
|
+
|
|
420
|
+
// Point light from the sphere
|
|
421
|
+
const sphereLight = new THREE.PointLight(0x4fd1c5, 0.8, 35);
|
|
422
|
+
sphereLight.position.copy(holoSphere.position);
|
|
423
|
+
scene.add(sphereLight);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function createGlowLights() {
|
|
427
|
+
// Floor-standing lamp posts
|
|
428
|
+
const lampPositions = [
|
|
429
|
+
[20, PLATFORM_HEIGHT, 15],
|
|
430
|
+
[-20, PLATFORM_HEIGHT, 15],
|
|
431
|
+
[20, PLATFORM_HEIGHT, -20],
|
|
432
|
+
[-20, PLATFORM_HEIGHT, -20],
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
lampPositions.forEach(pos => {
|
|
436
|
+
// Lamp post
|
|
437
|
+
const postGeo = new THREE.CylinderGeometry(0.15, 0.15, 6, 8);
|
|
438
|
+
const postMat = new THREE.MeshStandardMaterial({
|
|
439
|
+
color: 0x555555,
|
|
440
|
+
roughness: 0.3,
|
|
441
|
+
metalness: 0.7
|
|
442
|
+
});
|
|
443
|
+
const post = new THREE.Mesh(postGeo, postMat);
|
|
444
|
+
post.position.set(pos[0], pos[1] + 3, pos[2]);
|
|
445
|
+
post.castShadow = true;
|
|
446
|
+
scene.add(post);
|
|
129
447
|
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
448
|
+
// Lamp bulb
|
|
449
|
+
const bulbGeo = new THREE.SphereGeometry(0.4, 16, 16);
|
|
450
|
+
const bulbMat = new THREE.MeshBasicMaterial({
|
|
451
|
+
color: 0xffcc66,
|
|
452
|
+
transparent: true,
|
|
453
|
+
opacity: 0.9
|
|
454
|
+
});
|
|
455
|
+
const bulb = new THREE.Mesh(bulbGeo, bulbMat);
|
|
456
|
+
bulb.position.set(pos[0], pos[1] + 6.2, pos[2]);
|
|
457
|
+
scene.add(bulb);
|
|
135
458
|
|
|
136
|
-
|
|
137
|
-
|
|
459
|
+
// Point light
|
|
460
|
+
const light = new THREE.PointLight(0xffcc66, 0.5, 18);
|
|
461
|
+
light.position.set(pos[0], pos[1] + 6.2, pos[2]);
|
|
462
|
+
light.castShadow = false;
|
|
463
|
+
scene.add(light);
|
|
464
|
+
glowLights.push({ bulb, light, baseIntensity: 0.5 });
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function createFloatingParticles() {
|
|
469
|
+
const particleCount = 80;
|
|
470
|
+
const positions = new Float32Array(particleCount * 3);
|
|
471
|
+
|
|
472
|
+
for (let i = 0; i < particleCount; i++) {
|
|
473
|
+
positions[i * 3] = (Math.random() - 0.5) * PLATFORM_SIZE;
|
|
474
|
+
positions[i * 3 + 1] = PLATFORM_HEIGHT + 2 + Math.random() * WALL_HEIGHT;
|
|
475
|
+
positions[i * 3 + 2] = (Math.random() - 0.5) * PLATFORM_SIZE;
|
|
138
476
|
}
|
|
477
|
+
|
|
478
|
+
const geometry = new THREE.BufferGeometry();
|
|
479
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
480
|
+
|
|
481
|
+
const material = new THREE.PointsMaterial({
|
|
482
|
+
color: 0x88ffdd,
|
|
483
|
+
size: 0.12,
|
|
484
|
+
transparent: true,
|
|
485
|
+
opacity: 0.4,
|
|
486
|
+
blending: THREE.AdditiveBlending
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const particles = new THREE.Points(geometry, material);
|
|
490
|
+
scene.add(particles);
|
|
491
|
+
floatingParticles.push(particles);
|
|
139
492
|
}
|
|
140
493
|
|
|
141
|
-
function
|
|
494
|
+
function createAgentDesk(name, position, toolName = null) {
|
|
142
495
|
const color = getAgentColor(name);
|
|
143
496
|
const group = new THREE.Group();
|
|
144
497
|
group.position.copy(position);
|
|
498
|
+
group.position.y = PLATFORM_HEIGHT;
|
|
499
|
+
|
|
500
|
+
// Modern desk - white top with thin legs
|
|
501
|
+
const deskTopGeo = new THREE.BoxGeometry(5, 0.2, 3);
|
|
502
|
+
const deskMat = new THREE.MeshStandardMaterial({
|
|
503
|
+
color: 0xe8e8e8,
|
|
504
|
+
roughness: 0.3,
|
|
505
|
+
metalness: 0.1
|
|
506
|
+
});
|
|
507
|
+
const deskTop = new THREE.Mesh(deskTopGeo, deskMat);
|
|
508
|
+
deskTop.position.y = 2.5;
|
|
509
|
+
deskTop.castShadow = true;
|
|
510
|
+
deskTop.receiveShadow = true;
|
|
511
|
+
group.add(deskTop);
|
|
512
|
+
|
|
513
|
+
// Desk legs - thin metal
|
|
514
|
+
const legGeo = new THREE.CylinderGeometry(0.08, 0.08, 2.4, 8);
|
|
515
|
+
const legMat = new THREE.MeshStandardMaterial({
|
|
516
|
+
color: 0x999999,
|
|
517
|
+
roughness: 0.2,
|
|
518
|
+
metalness: 0.7
|
|
519
|
+
});
|
|
520
|
+
const legPositions = [
|
|
521
|
+
[-2.2, 1.2, -1.2],
|
|
522
|
+
[2.2, 1.2, -1.2],
|
|
523
|
+
[-2.2, 1.2, 1.2],
|
|
524
|
+
[2.2, 1.2, 1.2]
|
|
525
|
+
];
|
|
526
|
+
legPositions.forEach(pos => {
|
|
527
|
+
const leg = new THREE.Mesh(legGeo, legMat);
|
|
528
|
+
leg.position.set(...pos);
|
|
529
|
+
group.add(leg);
|
|
530
|
+
});
|
|
145
531
|
|
|
146
|
-
//
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
group.add(
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
532
|
+
// Modern chair - sleek
|
|
533
|
+
const chairSeatGeo = new THREE.BoxGeometry(1.8, 0.15, 1.8);
|
|
534
|
+
const chairMat = new THREE.MeshStandardMaterial({
|
|
535
|
+
color: 0x2a2a2a,
|
|
536
|
+
roughness: 0.5,
|
|
537
|
+
metalness: 0.2
|
|
538
|
+
});
|
|
539
|
+
const chairSeat = new THREE.Mesh(chairSeatGeo, chairMat);
|
|
540
|
+
chairSeat.position.set(0, 1.6, 3.2);
|
|
541
|
+
chairSeat.castShadow = true;
|
|
542
|
+
group.add(chairSeat);
|
|
543
|
+
|
|
544
|
+
// Chair back - curved look (box approximation)
|
|
545
|
+
const chairBackGeo = new THREE.BoxGeometry(1.8, 2.2, 0.15);
|
|
546
|
+
const chairBack = new THREE.Mesh(chairBackGeo, chairMat);
|
|
547
|
+
chairBack.position.set(0, 2.7, 4.1);
|
|
548
|
+
chairBack.castShadow = true;
|
|
549
|
+
group.add(chairBack);
|
|
550
|
+
|
|
551
|
+
// Chair post
|
|
552
|
+
const chairPostGeo = new THREE.CylinderGeometry(0.1, 0.1, 1.2, 8);
|
|
553
|
+
const chairPost = new THREE.Mesh(chairPostGeo, legMat);
|
|
554
|
+
chairPost.position.set(0, 0.9, 3.2);
|
|
555
|
+
group.add(chairPost);
|
|
556
|
+
|
|
557
|
+
// Chair base star
|
|
558
|
+
for (let i = 0; i < 5; i++) {
|
|
559
|
+
const armGeo = new THREE.CylinderGeometry(0.06, 0.06, 1.2, 6);
|
|
560
|
+
const arm = new THREE.Mesh(armGeo, legMat);
|
|
561
|
+
const angle = (i / 5) * Math.PI * 2;
|
|
562
|
+
arm.rotation.z = Math.PI / 2;
|
|
563
|
+
arm.position.set(
|
|
564
|
+
Math.cos(angle) * 0.5,
|
|
565
|
+
0.3,
|
|
566
|
+
3.2 + Math.sin(angle) * 0.5
|
|
567
|
+
);
|
|
568
|
+
arm.rotation.y = angle;
|
|
569
|
+
group.add(arm);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Person - Body (sitting, modern look)
|
|
573
|
+
const bodyGeo = new THREE.CylinderGeometry(0.6, 0.5, 2, 8);
|
|
574
|
+
const bodyMat = new THREE.MeshStandardMaterial({
|
|
575
|
+
color: color,
|
|
576
|
+
roughness: 0.6,
|
|
577
|
+
metalness: 0.05
|
|
578
|
+
});
|
|
579
|
+
const body = new THREE.Mesh(bodyGeo, bodyMat);
|
|
580
|
+
body.position.set(0, 2.7, 3.2);
|
|
581
|
+
body.castShadow = true;
|
|
582
|
+
group.add(body);
|
|
583
|
+
|
|
584
|
+
// Person - Head
|
|
585
|
+
const headGeo = new THREE.SphereGeometry(0.5, 16, 16);
|
|
586
|
+
const headMat = new THREE.MeshStandardMaterial({
|
|
587
|
+
color: 0xf5d0b0,
|
|
588
|
+
roughness: 0.7
|
|
589
|
+
});
|
|
590
|
+
const head = new THREE.Mesh(headGeo, headMat);
|
|
591
|
+
head.position.set(0, 4.0, 3.2);
|
|
592
|
+
head.castShadow = true;
|
|
593
|
+
group.add(head);
|
|
594
|
+
|
|
595
|
+
// Person - Arms on desk
|
|
596
|
+
const armObjGeo = new THREE.CylinderGeometry(0.12, 0.12, 1.8, 6);
|
|
597
|
+
const armMat = new THREE.MeshStandardMaterial({ color: color, roughness: 0.6 });
|
|
598
|
+
|
|
599
|
+
const leftArm = new THREE.Mesh(armObjGeo, armMat);
|
|
600
|
+
leftArm.rotation.z = Math.PI / 2;
|
|
601
|
+
leftArm.rotation.y = 0.3;
|
|
602
|
+
leftArm.position.set(-0.8, 2.8, 2);
|
|
603
|
+
group.add(leftArm);
|
|
604
|
+
|
|
605
|
+
const rightArm = new THREE.Mesh(armObjGeo, armMat);
|
|
606
|
+
rightArm.rotation.z = Math.PI / 2;
|
|
607
|
+
rightArm.rotation.y = -0.3;
|
|
608
|
+
rightArm.position.set(0.8, 2.8, 2);
|
|
609
|
+
group.add(rightArm);
|
|
610
|
+
|
|
611
|
+
// Monitor (modern flat screen)
|
|
612
|
+
const monitorStandGeo = new THREE.CylinderGeometry(0.5, 0.6, 0.1, 16);
|
|
613
|
+
const monitorMat = new THREE.MeshStandardMaterial({
|
|
614
|
+
color: 0x333333,
|
|
615
|
+
roughness: 0.3,
|
|
616
|
+
metalness: 0.5
|
|
617
|
+
});
|
|
618
|
+
const monitorStand = new THREE.Mesh(monitorStandGeo, monitorMat);
|
|
619
|
+
monitorStand.position.set(0, 2.65, 0.8);
|
|
620
|
+
group.add(monitorStand);
|
|
621
|
+
|
|
622
|
+
const monitorNeckGeo = new THREE.CylinderGeometry(0.08, 0.08, 1.2, 8);
|
|
623
|
+
const monitorNeck = new THREE.Mesh(monitorNeckGeo, monitorMat);
|
|
624
|
+
monitorNeck.position.set(0, 3.2, 0.8);
|
|
625
|
+
group.add(monitorNeck);
|
|
626
|
+
|
|
627
|
+
// Screen
|
|
628
|
+
const screenFrameGeo = new THREE.BoxGeometry(3, 1.8, 0.12);
|
|
629
|
+
const screenFrame = new THREE.Mesh(screenFrameGeo, monitorMat);
|
|
630
|
+
screenFrame.position.set(0, 4.0, 0.8);
|
|
631
|
+
screenFrame.castShadow = true;
|
|
632
|
+
group.add(screenFrame);
|
|
633
|
+
|
|
634
|
+
// Screen display (glowing)
|
|
635
|
+
const screenDisplayGeo = new THREE.PlaneGeometry(2.7, 1.5);
|
|
636
|
+
const screenDisplayMat = new THREE.MeshBasicMaterial({
|
|
637
|
+
color: 0x2a6b5e,
|
|
638
|
+
});
|
|
639
|
+
const screenDisplay = new THREE.Mesh(screenDisplayGeo, screenDisplayMat);
|
|
640
|
+
screenDisplay.position.set(0, 4.0, 0.87);
|
|
641
|
+
group.add(screenDisplay);
|
|
642
|
+
|
|
643
|
+
// Screen glow light
|
|
644
|
+
const screenLight = new THREE.PointLight(0x4fd1c5, 0.3, 6);
|
|
645
|
+
screenLight.position.set(0, 4.0, 1.5);
|
|
646
|
+
group.add(screenLight);
|
|
647
|
+
|
|
648
|
+
// Keyboard
|
|
649
|
+
const kbGeo = new THREE.BoxGeometry(1.6, 0.05, 0.5);
|
|
650
|
+
const kbMat = new THREE.MeshStandardMaterial({
|
|
651
|
+
color: 0x444444,
|
|
652
|
+
roughness: 0.5,
|
|
653
|
+
metalness: 0.3
|
|
654
|
+
});
|
|
655
|
+
const keyboard = new THREE.Mesh(kbGeo, kbMat);
|
|
656
|
+
keyboard.position.set(0, 2.63, 2);
|
|
657
|
+
group.add(keyboard);
|
|
658
|
+
|
|
659
|
+
// Name label sprite
|
|
188
660
|
const canvas = document.createElement('canvas');
|
|
189
661
|
const context = canvas.getContext('2d');
|
|
190
|
-
// High DPI canvas for crisp text
|
|
191
662
|
const scale = 2;
|
|
192
663
|
canvas.width = 512;
|
|
193
|
-
canvas.height = toolName ? 160 : 128;
|
|
664
|
+
canvas.height = toolName ? 160 : 128;
|
|
194
665
|
context.scale(scale, scale);
|
|
195
666
|
|
|
196
|
-
//
|
|
197
|
-
context.fillStyle = 'rgba(
|
|
667
|
+
// Frosted glass background
|
|
668
|
+
context.fillStyle = 'rgba(20, 60, 60, 0.85)';
|
|
198
669
|
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
199
670
|
context.fill();
|
|
200
671
|
|
|
201
|
-
//
|
|
202
|
-
context.
|
|
203
|
-
context.
|
|
672
|
+
// Subtle border
|
|
673
|
+
context.strokeStyle = 'rgba(79, 209, 197, 0.4)';
|
|
674
|
+
context.lineWidth = 1;
|
|
675
|
+
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
676
|
+
context.stroke();
|
|
677
|
+
|
|
678
|
+
context.font = 'bold 22px Arial';
|
|
679
|
+
context.fillStyle = '#e0f5f0';
|
|
204
680
|
context.textAlign = 'center';
|
|
205
681
|
context.textBaseline = 'middle';
|
|
206
682
|
context.fillText(name, 128, 24);
|
|
207
683
|
|
|
208
|
-
// Tool name text (if available)
|
|
209
684
|
if (toolName) {
|
|
210
|
-
context.font = 'italic
|
|
211
|
-
context.fillStyle = '#
|
|
685
|
+
context.font = 'italic 14px Arial';
|
|
686
|
+
context.fillStyle = '#4fd1c5';
|
|
212
687
|
context.fillText(toolName, 128, 56);
|
|
213
688
|
}
|
|
214
689
|
|
|
@@ -217,30 +692,21 @@ function createAgentHouse(name, position, toolName = null) {
|
|
|
217
692
|
texture.magFilter = THREE.LinearFilter;
|
|
218
693
|
const spriteMat = new THREE.SpriteMaterial({ map: texture });
|
|
219
694
|
const sprite = new THREE.Sprite(spriteMat);
|
|
220
|
-
sprite.position.set(0,
|
|
221
|
-
sprite.scale.set(
|
|
222
|
-
sprite.name = 'label';
|
|
695
|
+
sprite.position.set(0, 6.5, 2);
|
|
696
|
+
sprite.scale.set(7, toolName ? 2.2 : 1.8, 1);
|
|
697
|
+
sprite.name = 'label';
|
|
223
698
|
group.add(sprite);
|
|
224
699
|
|
|
225
|
-
// Path to house
|
|
226
|
-
const pathGeo = new THREE.PlaneGeometry(2, 8);
|
|
227
|
-
const pathMat = new THREE.MeshStandardMaterial({ color: 0xD2B48C });
|
|
228
|
-
const path = new THREE.Mesh(pathGeo, pathMat);
|
|
229
|
-
path.rotation.x = -Math.PI / 2;
|
|
230
|
-
path.position.set(0, 0.02, 7);
|
|
231
|
-
group.add(path);
|
|
232
|
-
|
|
233
700
|
scene.add(group);
|
|
234
701
|
agentMeshes.set(name, group);
|
|
235
702
|
|
|
236
703
|
return group;
|
|
237
704
|
}
|
|
238
705
|
|
|
239
|
-
function
|
|
240
|
-
const sprite =
|
|
706
|
+
function updateDeskLabel(desk, name, toolName = null) {
|
|
707
|
+
const sprite = desk.getObjectByName('label');
|
|
241
708
|
if (!sprite) return;
|
|
242
709
|
|
|
243
|
-
// Create new canvas with updated text
|
|
244
710
|
const canvas = document.createElement('canvas');
|
|
245
711
|
const context = canvas.getContext('2d');
|
|
246
712
|
const scale = 2;
|
|
@@ -248,67 +714,73 @@ function updateHouseLabel(house, name, toolName = null) {
|
|
|
248
714
|
canvas.height = toolName ? 160 : 128;
|
|
249
715
|
context.scale(scale, scale);
|
|
250
716
|
|
|
251
|
-
|
|
252
|
-
context.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
|
717
|
+
context.fillStyle = 'rgba(20, 60, 60, 0.85)';
|
|
253
718
|
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
254
719
|
context.fill();
|
|
255
720
|
|
|
256
|
-
|
|
257
|
-
context.
|
|
258
|
-
context.
|
|
721
|
+
context.strokeStyle = 'rgba(79, 209, 197, 0.4)';
|
|
722
|
+
context.lineWidth = 1;
|
|
723
|
+
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
724
|
+
context.stroke();
|
|
725
|
+
|
|
726
|
+
context.font = 'bold 22px Arial';
|
|
727
|
+
context.fillStyle = '#e0f5f0';
|
|
259
728
|
context.textAlign = 'center';
|
|
260
729
|
context.textBaseline = 'middle';
|
|
261
730
|
context.fillText(name, 128, 24);
|
|
262
731
|
|
|
263
|
-
// Tool name text (if available)
|
|
264
732
|
if (toolName) {
|
|
265
|
-
context.font = 'italic
|
|
266
|
-
context.fillStyle = '#
|
|
733
|
+
context.font = 'italic 14px Arial';
|
|
734
|
+
context.fillStyle = '#4fd1c5';
|
|
267
735
|
context.fillText(toolName, 128, 56);
|
|
268
736
|
}
|
|
269
737
|
|
|
270
|
-
// Update texture
|
|
271
738
|
const texture = new THREE.CanvasTexture(canvas);
|
|
272
739
|
texture.minFilter = THREE.LinearFilter;
|
|
273
740
|
texture.magFilter = THREE.LinearFilter;
|
|
274
741
|
sprite.material.map = texture;
|
|
275
742
|
sprite.material.needsUpdate = true;
|
|
276
743
|
|
|
277
|
-
|
|
278
|
-
sprite.
|
|
279
|
-
sprite.scale.set(8, toolName ? 2.5 : 2, 1);
|
|
744
|
+
sprite.position.set(0, 6.5, 2);
|
|
745
|
+
sprite.scale.set(7, toolName ? 2.2 : 1.8, 1);
|
|
280
746
|
}
|
|
281
747
|
|
|
282
748
|
function createMessageParticle(fromPos, toPos) {
|
|
283
|
-
const particleGeo = new THREE.SphereGeometry(0.
|
|
284
|
-
const particleMat = new THREE.
|
|
285
|
-
color:
|
|
286
|
-
|
|
287
|
-
|
|
749
|
+
const particleGeo = new THREE.SphereGeometry(0.35, 12, 12);
|
|
750
|
+
const particleMat = new THREE.MeshBasicMaterial({
|
|
751
|
+
color: 0xff6b6b,
|
|
752
|
+
transparent: true,
|
|
753
|
+
opacity: 0.95
|
|
288
754
|
});
|
|
289
755
|
const particle = new THREE.Mesh(particleGeo, particleMat);
|
|
290
756
|
|
|
291
757
|
particle.position.copy(fromPos);
|
|
292
|
-
particle.position.y +=
|
|
758
|
+
particle.position.y += 5;
|
|
759
|
+
|
|
760
|
+
// Add a glow point light that follows particle
|
|
761
|
+
const glow = new THREE.PointLight(0xff6b6b, 1.0, 10);
|
|
762
|
+
particle.add(glow);
|
|
293
763
|
|
|
294
764
|
scene.add(particle);
|
|
295
765
|
|
|
296
|
-
// Animate particle
|
|
297
766
|
const startTime = Date.now();
|
|
298
|
-
const duration =
|
|
767
|
+
const duration = 1500;
|
|
299
768
|
|
|
300
769
|
function animateParticle() {
|
|
301
770
|
const elapsed = Date.now() - startTime;
|
|
302
771
|
const progress = Math.min(elapsed / duration, 1);
|
|
303
772
|
|
|
304
773
|
particle.position.lerpVectors(
|
|
305
|
-
new THREE.Vector3(fromPos.x, fromPos.y +
|
|
306
|
-
new THREE.Vector3(toPos.x, toPos.y +
|
|
774
|
+
new THREE.Vector3(fromPos.x, fromPos.y + 5, fromPos.z),
|
|
775
|
+
new THREE.Vector3(toPos.x, toPos.y + 5, toPos.z),
|
|
307
776
|
progress
|
|
308
777
|
);
|
|
309
778
|
|
|
310
|
-
|
|
311
|
-
|
|
779
|
+
particle.position.y += Math.sin(progress * Math.PI) * 2;
|
|
780
|
+
|
|
781
|
+
// Pulse size
|
|
782
|
+
const pulse = Math.sin(progress * Math.PI) * 0.2 + 1;
|
|
783
|
+
particle.scale.setScalar(pulse);
|
|
312
784
|
|
|
313
785
|
if (progress < 1) {
|
|
314
786
|
requestAnimationFrame(animateParticle);
|
|
@@ -324,48 +796,58 @@ function createConnectionLine(fromPos, toPos) {
|
|
|
324
796
|
const startPos = new THREE.Vector3(fromPos.x, fromPos.y + 5, fromPos.z);
|
|
325
797
|
const endPos = new THREE.Vector3(toPos.x, toPos.y + 5, toPos.z);
|
|
326
798
|
|
|
799
|
+
// Create curved line with points
|
|
800
|
+
const mid = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5);
|
|
801
|
+
mid.y += 2;
|
|
802
|
+
|
|
803
|
+
const curve = new THREE.QuadraticBezierCurve3(startPos, mid, endPos);
|
|
804
|
+
const points = curve.getPoints(50);
|
|
805
|
+
|
|
806
|
+
// Main line - thicker, glowing red
|
|
327
807
|
const material = new THREE.LineBasicMaterial({
|
|
328
808
|
color: 0xff6b6b,
|
|
329
|
-
opacity: 0.
|
|
809
|
+
opacity: 0.7,
|
|
330
810
|
transparent: true,
|
|
331
811
|
linewidth: 3
|
|
332
812
|
});
|
|
333
813
|
|
|
334
|
-
const points = [startPos, endPos];
|
|
335
|
-
|
|
336
814
|
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
337
815
|
const line = new THREE.Line(geometry, material);
|
|
338
816
|
|
|
339
817
|
scene.add(line);
|
|
340
818
|
connectionLines.push(line);
|
|
341
819
|
|
|
342
|
-
// Add
|
|
820
|
+
// Add small chevron markers along the path to show direction
|
|
343
821
|
const direction = new THREE.Vector3().subVectors(endPos, startPos).normalize();
|
|
344
|
-
const arrowPos = endPos.clone().sub(direction.clone().multiplyScalar(5));
|
|
345
|
-
|
|
346
|
-
const arrowGeometry = new THREE.ConeGeometry(0.5, 1.5, 8);
|
|
347
|
-
const arrowMaterial = new THREE.MeshStandardMaterial({
|
|
348
|
-
color: 0xff6b6b,
|
|
349
|
-
emissive: 0xff6b6b,
|
|
350
|
-
emissiveIntensity: 0.3
|
|
351
|
-
});
|
|
352
|
-
const arrowhead = new THREE.Mesh(arrowGeometry, arrowMaterial);
|
|
353
|
-
|
|
354
|
-
arrowhead.position.copy(arrowPos);
|
|
355
|
-
|
|
356
|
-
// Orient arrow to point along the line
|
|
357
822
|
const up = new THREE.Vector3(0, 1, 0);
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
823
|
+
const numMarkers = 4;
|
|
824
|
+
for (let i = 0; i < numMarkers; i++) {
|
|
825
|
+
const t = (i + 1) / (numMarkers + 1);
|
|
826
|
+
const markerPos = curve.getPoint(t);
|
|
827
|
+
|
|
828
|
+
const markerGeo = new THREE.ConeGeometry(0.25, 0.7, 8);
|
|
829
|
+
const markerMat = new THREE.MeshBasicMaterial({
|
|
830
|
+
color: 0xff6b6b,
|
|
831
|
+
transparent: true,
|
|
832
|
+
opacity: 0.5
|
|
833
|
+
});
|
|
834
|
+
const marker = new THREE.Mesh(markerGeo, markerMat);
|
|
835
|
+
|
|
836
|
+
// Get tangent at this point for direction
|
|
837
|
+
const tangent = curve.getTangent(t);
|
|
838
|
+
marker.position.copy(markerPos);
|
|
839
|
+
|
|
840
|
+
const markerQuat = new THREE.Quaternion();
|
|
841
|
+
markerQuat.setFromUnitVectors(up, tangent);
|
|
842
|
+
marker.setRotationFromQuaternion(markerQuat);
|
|
843
|
+
|
|
844
|
+
scene.add(marker);
|
|
845
|
+
connectionLines.push(marker);
|
|
846
|
+
}
|
|
364
847
|
|
|
365
|
-
// Send particle
|
|
366
848
|
setTimeout(() => {
|
|
367
849
|
createMessageParticle(fromPos, toPos);
|
|
368
|
-
},
|
|
850
|
+
}, 50);
|
|
369
851
|
}
|
|
370
852
|
|
|
371
853
|
function clearConnections() {
|
|
@@ -377,49 +859,47 @@ function updateVillage() {
|
|
|
377
859
|
clearConnections();
|
|
378
860
|
|
|
379
861
|
// Use recipients (from coworkers.db) as the authoritative list of agents
|
|
380
|
-
// This ensures we only show houses for registered coworkers
|
|
381
862
|
const allAgents = new Set([config.user.toLowerCase(), ...recipients.map(r => r.toLowerCase())]);
|
|
382
863
|
|
|
383
|
-
// Also add message participants
|
|
864
|
+
// Also add message participants
|
|
384
865
|
messages.forEach(m => {
|
|
385
866
|
allAgents.add(m.sender.toLowerCase());
|
|
386
867
|
allAgents.add(m.recipient.toLowerCase());
|
|
387
868
|
});
|
|
388
869
|
|
|
389
|
-
// Arrange agents in a circle
|
|
870
|
+
// Arrange agents in a circle on the platform
|
|
390
871
|
const agents = Array.from(allAgents);
|
|
391
|
-
const radius =
|
|
872
|
+
const radius = Math.min(20, Math.max(10, agents.length * 3));
|
|
392
873
|
|
|
393
874
|
agents.forEach((agent, index) => {
|
|
394
|
-
const angle = (index / agents.length) * Math.PI * 2;
|
|
875
|
+
const angle = (index / agents.length) * Math.PI * 2 - Math.PI / 2;
|
|
395
876
|
const x = Math.cos(angle) * radius;
|
|
396
877
|
const z = Math.sin(angle) * radius;
|
|
397
878
|
const position = new THREE.Vector3(x, 0, z);
|
|
398
879
|
|
|
399
|
-
// Get tool name for this agent from avatar states
|
|
400
880
|
const avatarState = avatarStates[agent.toLowerCase()];
|
|
401
881
|
const toolName = avatarState?.tool_name || null;
|
|
402
882
|
|
|
403
883
|
if (!agentMeshes.has(agent)) {
|
|
404
|
-
|
|
884
|
+
const group = createAgentDesk(agent, position, toolName);
|
|
885
|
+
// Face desk toward center
|
|
886
|
+
group.lookAt(new THREE.Vector3(0, PLATFORM_HEIGHT, 0));
|
|
405
887
|
} else {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// Update label with current tool name
|
|
411
|
-
updateHouseLabel(house, agent, toolName);
|
|
888
|
+
const desk = agentMeshes.get(agent);
|
|
889
|
+
desk.position.set(x, PLATFORM_HEIGHT, z);
|
|
890
|
+
desk.lookAt(new THREE.Vector3(0, PLATFORM_HEIGHT, 0));
|
|
891
|
+
updateDeskLabel(desk, agent, toolName);
|
|
412
892
|
}
|
|
413
893
|
});
|
|
414
894
|
|
|
415
|
-
// Remove
|
|
416
|
-
agentMeshes.forEach((
|
|
895
|
+
// Remove desks for coworkers that no longer exist
|
|
896
|
+
agentMeshes.forEach((desk, name) => {
|
|
417
897
|
if (!allAgents.has(name)) {
|
|
418
898
|
// Remove from scene
|
|
419
|
-
scene.remove(
|
|
899
|
+
scene.remove(desk);
|
|
420
900
|
|
|
421
901
|
// Dispose of geometries and materials to prevent memory leaks
|
|
422
|
-
|
|
902
|
+
desk.traverse((child) => {
|
|
423
903
|
if (child.isMesh) {
|
|
424
904
|
child.geometry.dispose();
|
|
425
905
|
if (child.material) {
|
|
@@ -439,23 +919,47 @@ function updateVillage() {
|
|
|
439
919
|
|
|
440
920
|
// Create connections for unread messages only
|
|
441
921
|
allMessages.forEach(msg => {
|
|
442
|
-
const
|
|
443
|
-
const
|
|
922
|
+
const fromDesk = agentMeshes.get(msg.sender.toLowerCase());
|
|
923
|
+
const toDesk = agentMeshes.get(msg.recipient.toLowerCase());
|
|
444
924
|
|
|
445
|
-
if (
|
|
925
|
+
if (fromDesk && toDesk && !msg.read) {
|
|
446
926
|
createConnectionLine(
|
|
447
|
-
|
|
448
|
-
|
|
927
|
+
fromDesk.position,
|
|
928
|
+
toDesk.position
|
|
449
929
|
);
|
|
450
930
|
}
|
|
451
931
|
});
|
|
452
932
|
|
|
453
|
-
// Update
|
|
454
|
-
|
|
933
|
+
// Update desk labels with unread indicators
|
|
934
|
+
updateDeskLabels();
|
|
455
935
|
}
|
|
456
936
|
|
|
457
937
|
function animate() {
|
|
458
938
|
requestAnimationFrame(animate);
|
|
939
|
+
|
|
940
|
+
const time = Date.now() * 0.001;
|
|
941
|
+
|
|
942
|
+
// Rotate holographic sphere
|
|
943
|
+
if (holoSphere) {
|
|
944
|
+
holoSphere.rotation.y = time * 0.15;
|
|
945
|
+
holoSphere.rotation.x = Math.sin(time * 0.1) * 0.1;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Animate floating particles
|
|
949
|
+
floatingParticles.forEach(particles => {
|
|
950
|
+
const positions = particles.geometry.attributes.position.array;
|
|
951
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
952
|
+
positions[i + 1] += Math.sin(time + positions[i] * 0.1) * 0.003;
|
|
953
|
+
}
|
|
954
|
+
particles.geometry.attributes.position.needsUpdate = true;
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
// Subtle glow pulse on lamps
|
|
958
|
+
glowLights.forEach((item, i) => {
|
|
959
|
+
const pulse = Math.sin(time * 1.5 + i) * 0.15 + 1;
|
|
960
|
+
item.light.intensity = item.baseIntensity * pulse;
|
|
961
|
+
});
|
|
962
|
+
|
|
459
963
|
controls.update();
|
|
460
964
|
renderer.render(scene, camera);
|
|
461
965
|
}
|
|
@@ -467,14 +971,12 @@ function onWindowResize() {
|
|
|
467
971
|
camera.updateProjectionMatrix();
|
|
468
972
|
renderer.setSize(width, height);
|
|
469
973
|
|
|
470
|
-
// Adjust camera position for better mobile view
|
|
471
974
|
if (width < 768) {
|
|
472
|
-
|
|
473
|
-
camera.position.
|
|
474
|
-
|
|
475
|
-
controls.minDistance = 30; // Prevent zooming too close on mobile
|
|
975
|
+
camera.position.y = Math.max(camera.position.y, 40);
|
|
976
|
+
camera.position.z = Math.max(camera.position.z, 50);
|
|
977
|
+
controls.minDistance = 35;
|
|
476
978
|
} else {
|
|
477
|
-
controls.minDistance =
|
|
979
|
+
controls.minDistance = 25;
|
|
478
980
|
}
|
|
479
981
|
}
|
|
480
982
|
|
|
@@ -609,9 +1111,9 @@ function updateUI() {
|
|
|
609
1111
|
});
|
|
610
1112
|
}
|
|
611
1113
|
|
|
612
|
-
// Update
|
|
1114
|
+
// Update desk dialog if it's open
|
|
613
1115
|
if (document.getElementById('house-dialog').classList.contains('active')) {
|
|
614
|
-
|
|
1116
|
+
updateDeskDialogContent();
|
|
615
1117
|
}
|
|
616
1118
|
}
|
|
617
1119
|
|
|
@@ -672,23 +1174,23 @@ async function sendMessage() {
|
|
|
672
1174
|
}
|
|
673
1175
|
}
|
|
674
1176
|
|
|
675
|
-
//
|
|
676
|
-
function
|
|
677
|
-
|
|
1177
|
+
// Desk click handler
|
|
1178
|
+
function onDeskClick(event) {
|
|
1179
|
+
handleDeskInteraction(event.clientX, event.clientY);
|
|
678
1180
|
}
|
|
679
1181
|
|
|
680
1182
|
// Touch handlers for mobile
|
|
681
1183
|
let touchStartX = 0;
|
|
682
1184
|
let touchStartY = 0;
|
|
683
1185
|
|
|
684
|
-
function
|
|
1186
|
+
function onDeskTouchStart(event) {
|
|
685
1187
|
if (event.touches.length === 1) {
|
|
686
1188
|
touchStartX = event.touches[0].clientX;
|
|
687
1189
|
touchStartY = event.touches[0].clientY;
|
|
688
1190
|
}
|
|
689
1191
|
}
|
|
690
1192
|
|
|
691
|
-
function
|
|
1193
|
+
function onDeskTouchEnd(event) {
|
|
692
1194
|
if (event.changedTouches.length === 1) {
|
|
693
1195
|
const touchEndX = event.changedTouches[0].clientX;
|
|
694
1196
|
const touchEndY = event.changedTouches[0].clientY;
|
|
@@ -701,13 +1203,13 @@ function onHouseTouchEnd(event) {
|
|
|
701
1203
|
|
|
702
1204
|
// Only trigger if touch didn't move much (tap vs swipe)
|
|
703
1205
|
if (moveDistance < 20) {
|
|
704
|
-
|
|
1206
|
+
handleDeskInteraction(touchEndX, touchEndY);
|
|
705
1207
|
}
|
|
706
1208
|
}
|
|
707
1209
|
}
|
|
708
1210
|
|
|
709
|
-
// Common
|
|
710
|
-
function
|
|
1211
|
+
// Common desk interaction handler
|
|
1212
|
+
function handleDeskInteraction(clientX, clientY) {
|
|
711
1213
|
// Calculate normalized device coordinates
|
|
712
1214
|
const rect = renderer.domElement.getBoundingClientRect();
|
|
713
1215
|
const x = ((clientX - rect.left) / rect.width) * 2 - 1;
|
|
@@ -718,34 +1220,34 @@ function handleHouseInteraction(clientX, clientY) {
|
|
|
718
1220
|
|
|
719
1221
|
raycaster.setFromCamera(mouse, camera);
|
|
720
1222
|
|
|
721
|
-
// Get all
|
|
722
|
-
const
|
|
1223
|
+
// Get all desk meshes
|
|
1224
|
+
const deskMeshes = [];
|
|
723
1225
|
agentMeshes.forEach((group, name) => {
|
|
724
1226
|
group.children.forEach(child => {
|
|
725
1227
|
if (child.isMesh && !child.userData.isBubble && !child.userData.isCup) {
|
|
726
1228
|
child.userData.agentName = name;
|
|
727
|
-
|
|
1229
|
+
deskMeshes.push(child);
|
|
728
1230
|
}
|
|
729
1231
|
});
|
|
730
1232
|
});
|
|
731
1233
|
|
|
732
|
-
const intersects = raycaster.intersectObjects(
|
|
1234
|
+
const intersects = raycaster.intersectObjects(deskMeshes);
|
|
733
1235
|
|
|
734
1236
|
if (intersects.length > 0) {
|
|
735
1237
|
const agentName = intersects[0].object.userData.agentName;
|
|
736
1238
|
if (agentName) {
|
|
737
|
-
|
|
1239
|
+
showDeskDialog(agentName);
|
|
738
1240
|
}
|
|
739
1241
|
}
|
|
740
1242
|
}
|
|
741
1243
|
|
|
742
|
-
// Global variable to track current agent for
|
|
743
|
-
let
|
|
1244
|
+
// Global variable to track current agent for desk dialog
|
|
1245
|
+
let currentDeskAgent = null;
|
|
744
1246
|
let currentTab = 'received';
|
|
745
1247
|
|
|
746
1248
|
// Show dialog with messages for a specific agent
|
|
747
|
-
async function
|
|
748
|
-
|
|
1249
|
+
async function showDeskDialog(agentName) {
|
|
1250
|
+
currentDeskAgent = agentName.toLowerCase();
|
|
749
1251
|
currentTab = 'received'; // Default to received tab
|
|
750
1252
|
|
|
751
1253
|
const dialog = document.getElementById('house-dialog');
|
|
@@ -785,35 +1287,35 @@ window.switchTab = function(tab) {
|
|
|
785
1287
|
document.getElementById('tab-sent').classList.toggle('active', tab === 'sent');
|
|
786
1288
|
|
|
787
1289
|
// Update content
|
|
788
|
-
|
|
1290
|
+
updateDeskDialogContent();
|
|
789
1291
|
};
|
|
790
1292
|
|
|
791
|
-
function
|
|
1293
|
+
function updateDeskDialogContent() {
|
|
792
1294
|
const content = document.getElementById('house-dialog-content');
|
|
793
1295
|
|
|
794
|
-
if (!
|
|
1296
|
+
if (!currentDeskAgent) return;
|
|
795
1297
|
|
|
796
1298
|
// Filter messages based on current tab - FROM THE AGENT'S PERSPECTIVE
|
|
797
1299
|
let filteredMessages;
|
|
798
1300
|
if (currentTab === 'received') {
|
|
799
1301
|
// Messages RECEIVED BY the agent (sent TO the agent)
|
|
800
1302
|
filteredMessages = allMessages.filter(m =>
|
|
801
|
-
m.recipient.toLowerCase() ===
|
|
1303
|
+
m.recipient.toLowerCase() === currentDeskAgent
|
|
802
1304
|
);
|
|
803
1305
|
} else {
|
|
804
1306
|
// Messages SENT BY the agent
|
|
805
1307
|
filteredMessages = allMessages.filter(m =>
|
|
806
|
-
m.sender.toLowerCase() ===
|
|
1308
|
+
m.sender.toLowerCase() === currentDeskAgent
|
|
807
1309
|
);
|
|
808
1310
|
}
|
|
809
1311
|
|
|
810
1312
|
// Update count badges
|
|
811
1313
|
const receivedCount = allMessages.filter(m =>
|
|
812
|
-
m.recipient.toLowerCase() ===
|
|
1314
|
+
m.recipient.toLowerCase() === currentDeskAgent
|
|
813
1315
|
).length;
|
|
814
1316
|
|
|
815
1317
|
const sentCount = allMessages.filter(m =>
|
|
816
|
-
m.sender.toLowerCase() ===
|
|
1318
|
+
m.sender.toLowerCase() === currentDeskAgent
|
|
817
1319
|
).length;
|
|
818
1320
|
|
|
819
1321
|
const receivedBadge = document.getElementById('received-count');
|
|
@@ -843,35 +1345,30 @@ function updateHouseDialogContent() {
|
|
|
843
1345
|
}
|
|
844
1346
|
}
|
|
845
1347
|
|
|
846
|
-
window.
|
|
1348
|
+
window.closeDeskDialog = function() {
|
|
847
1349
|
document.getElementById('house-dialog').classList.remove('active');
|
|
848
1350
|
};
|
|
849
1351
|
|
|
850
|
-
// Update
|
|
851
|
-
function
|
|
1352
|
+
// Update desk labels to show unread indicators and tool names
|
|
1353
|
+
function updateDeskLabels() {
|
|
852
1354
|
agentMeshes.forEach((group, name) => {
|
|
853
|
-
// Check for unread messages SENT TO this agent (messages they haven't read)
|
|
854
1355
|
const unreadCount = allMessages.filter(m =>
|
|
855
1356
|
m.recipient.toLowerCase() === name.toLowerCase() &&
|
|
856
1357
|
m.sender.toLowerCase() === config.user.toLowerCase() &&
|
|
857
1358
|
!m.read
|
|
858
1359
|
).length;
|
|
859
1360
|
|
|
860
|
-
// Also check if this agent has sent unread messages TO user
|
|
861
1361
|
const unreadFromAgent = allMessages.filter(m =>
|
|
862
1362
|
m.sender.toLowerCase() === name.toLowerCase() &&
|
|
863
1363
|
m.recipient.toLowerCase() === config.user.toLowerCase() &&
|
|
864
1364
|
!m.read
|
|
865
1365
|
).length;
|
|
866
1366
|
|
|
867
|
-
// Get tool name for this agent
|
|
868
1367
|
const avatarState = avatarStates[name.toLowerCase()];
|
|
869
1368
|
const toolName = avatarState?.tool_name || null;
|
|
870
1369
|
|
|
871
|
-
// Find the sprite label
|
|
872
1370
|
const sprite = group.children.find(c => c.isSprite);
|
|
873
1371
|
if (sprite) {
|
|
874
|
-
// Update the canvas texture - high DPI for crisp text
|
|
875
1372
|
const canvas = document.createElement('canvas');
|
|
876
1373
|
const context = canvas.getContext('2d');
|
|
877
1374
|
const scale = 2;
|
|
@@ -879,40 +1376,43 @@ function updateHouseLabels() {
|
|
|
879
1376
|
canvas.height = toolName ? 160 : 128;
|
|
880
1377
|
context.scale(scale, scale);
|
|
881
1378
|
|
|
882
|
-
// Background -
|
|
1379
|
+
// Background - themed colors for state
|
|
883
1380
|
if (unreadFromAgent > 0) {
|
|
884
|
-
|
|
885
|
-
context.fillStyle = 'rgba(220, 53, 69, 0.9)';
|
|
1381
|
+
context.fillStyle = 'rgba(220, 80, 80, 0.85)';
|
|
886
1382
|
} else if (unreadCount > 0) {
|
|
887
|
-
|
|
888
|
-
context.fillStyle = 'rgba(0, 123, 255, 0.9)';
|
|
1383
|
+
context.fillStyle = 'rgba(59, 130, 180, 0.85)';
|
|
889
1384
|
} else {
|
|
890
|
-
|
|
891
|
-
context.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
|
1385
|
+
context.fillStyle = 'rgba(20, 60, 60, 0.85)';
|
|
892
1386
|
}
|
|
893
1387
|
context.roundRect(0, 0, 350, toolName ? 80 : 64, 16);
|
|
894
1388
|
context.fill();
|
|
895
1389
|
|
|
896
|
-
//
|
|
897
|
-
context.
|
|
898
|
-
|
|
1390
|
+
// Border
|
|
1391
|
+
context.strokeStyle = unreadFromAgent > 0
|
|
1392
|
+
? 'rgba(255, 120, 120, 0.6)'
|
|
1393
|
+
: unreadCount > 0
|
|
1394
|
+
? 'rgba(100, 180, 255, 0.6)'
|
|
1395
|
+
: 'rgba(79, 209, 197, 0.3)';
|
|
1396
|
+
context.lineWidth = 1;
|
|
1397
|
+
context.roundRect(0, 0, 350, toolName ? 80 : 64, 16);
|
|
1398
|
+
context.stroke();
|
|
1399
|
+
|
|
1400
|
+
context.font = 'bold 22px Arial';
|
|
1401
|
+
context.fillStyle = '#e0f5f0';
|
|
899
1402
|
context.textAlign = 'center';
|
|
900
1403
|
context.textBaseline = 'middle';
|
|
901
1404
|
|
|
902
1405
|
if (unreadFromAgent > 0) {
|
|
903
|
-
|
|
904
|
-
context.fillText(`${name} 🔴 ${unreadFromAgent}`, 175, 24);
|
|
1406
|
+
context.fillText(`${name} ${unreadFromAgent}`, 175, 24);
|
|
905
1407
|
} else if (unreadCount > 0) {
|
|
906
|
-
|
|
907
|
-
context.fillText(`${name} 📤 ${unreadCount}`, 175, 24);
|
|
1408
|
+
context.fillText(`${name} ${unreadCount}`, 175, 24);
|
|
908
1409
|
} else {
|
|
909
1410
|
context.fillText(name, 175, 24);
|
|
910
1411
|
}
|
|
911
1412
|
|
|
912
|
-
// Tool name text (if available)
|
|
913
1413
|
if (toolName) {
|
|
914
|
-
context.font = 'italic
|
|
915
|
-
context.fillStyle = '#
|
|
1414
|
+
context.font = 'italic 14px Arial';
|
|
1415
|
+
context.fillStyle = '#4fd1c5';
|
|
916
1416
|
context.fillText(toolName, 175, 56);
|
|
917
1417
|
}
|
|
918
1418
|
|
|
@@ -922,9 +1422,8 @@ function updateHouseLabels() {
|
|
|
922
1422
|
sprite.material.map = texture;
|
|
923
1423
|
sprite.material.needsUpdate = true;
|
|
924
1424
|
|
|
925
|
-
|
|
926
|
-
sprite.
|
|
927
|
-
sprite.scale.set(8, toolName ? 2.5 : 2, 1);
|
|
1425
|
+
sprite.position.set(0, 6.5, 2);
|
|
1426
|
+
sprite.scale.set(7, toolName ? 2.2 : 1.8, 1);
|
|
928
1427
|
}
|
|
929
1428
|
});
|
|
930
1429
|
}
|