let-them-talk 4.3.0 → 5.2.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 +640 -582
- package/README.md +592 -415
- package/cli.js +1089 -589
- package/conversation-templates/autonomous-feature.json +22 -0
- package/conversation-templates/code-review.json +21 -11
- package/conversation-templates/debug-squad.json +21 -11
- package/conversation-templates/feature-build.json +21 -11
- package/conversation-templates/research-write.json +21 -11
- package/dashboard.html +9250 -7964
- package/dashboard.js +1071 -29
- package/office/building-interior.js +261 -0
- package/office/car-hud.js +368 -0
- package/office/daynight.js +221 -0
- package/office/economy-hud.js +432 -0
- package/office/economy-ui.js +238 -0
- package/office/environment.js +818 -808
- package/office/fast-travel.js +215 -0
- package/office/hq-building.js +295 -0
- package/office/index.js +1095 -1046
- package/office/instancing.js +160 -0
- package/office/lod-manager.js +165 -0
- package/office/multiplayer-hud.js +428 -0
- package/office/net-client.js +299 -0
- package/office/particles.js +172 -0
- package/office/player.js +658 -658
- package/office/post-processing.js +82 -0
- package/office/sky.js +319 -0
- package/office/street-furniture.js +308 -0
- package/office/vehicle.js +455 -0
- package/package.json +59 -59
- package/server.js +7190 -4685
- package/conversation-templates/managed-team.json +0 -12
package/office/index.js
CHANGED
|
@@ -1,1046 +1,1095 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
2
|
-
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
3
|
-
import { S } from './state.js';
|
|
4
|
-
import { DESK_POSITIONS, DRESSING_ROOM_POS, DRESSING_ROOM_ENTRANCE, REST_AREA_POS, REST_AREA_ENTRANCE } from './constants.js';
|
|
5
|
-
import { initScene } from './scene.js';
|
|
6
|
-
import { buildEnvironment, updateTVScreen } from './environment.js';
|
|
7
|
-
import { updateAgent } from './animation.js';
|
|
8
|
-
import { syncAgents, processMessages, walkTo, navigateTo, showBubble } from './agents.js';
|
|
9
|
-
// Side-effect: registers window.officeGetAppearance
|
|
10
|
-
import './appearance.js';
|
|
11
|
-
import { spawnPlayer, despawnPlayer, isPlayerMode, updatePlayer, savePlayerAppearance, getPlayerAppearance, getPlayer, invalidateColliders } from './player.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
input.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
agent.
|
|
273
|
-
agent
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
agent.
|
|
278
|
-
agent
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
showBubble(agent, '
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
});
|
|
339
|
-
break;
|
|
340
|
-
|
|
341
|
-
case '
|
|
342
|
-
|
|
343
|
-
if (typeof window.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
S.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
S.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
S.
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
var
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
S.
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
//
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
S.
|
|
768
|
-
S.
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
S.
|
|
772
|
-
S.
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
//
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
//
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
//
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
var
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
window.
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
var
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
var
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
header
|
|
903
|
-
header.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
}
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
3
|
+
import { S } from './state.js';
|
|
4
|
+
import { DESK_POSITIONS, DRESSING_ROOM_POS, DRESSING_ROOM_ENTRANCE, REST_AREA_POS, REST_AREA_ENTRANCE } from './constants.js';
|
|
5
|
+
import { initScene } from './scene.js';
|
|
6
|
+
import { buildEnvironment, updateTVScreen } from './environment.js';
|
|
7
|
+
import { updateAgent } from './animation.js';
|
|
8
|
+
import { syncAgents, processMessages, walkTo, navigateTo, showBubble } from './agents.js';
|
|
9
|
+
// Side-effect: registers window.officeGetAppearance
|
|
10
|
+
import './appearance.js';
|
|
11
|
+
import { spawnPlayer, despawnPlayer, isPlayerMode, updatePlayer, savePlayerAppearance, getPlayerAppearance, getPlayer, invalidateColliders } from './player.js';
|
|
12
|
+
// City modules loaded on demand (not at startup — would kill campus FPS)
|
|
13
|
+
var _cityMods = null;
|
|
14
|
+
function getCityMods() {
|
|
15
|
+
if (_cityMods) return _cityMods;
|
|
16
|
+
_cityMods = { loaded: false };
|
|
17
|
+
Promise.all([
|
|
18
|
+
import('./vehicle.js'),
|
|
19
|
+
import('./economy-ui.js'),
|
|
20
|
+
import('./daynight.js'),
|
|
21
|
+
]).then(function(mods) {
|
|
22
|
+
_cityMods.vehicle = mods[0];
|
|
23
|
+
_cityMods.economy = mods[1];
|
|
24
|
+
_cityMods.daynight = mods[2];
|
|
25
|
+
_cityMods.loaded = true;
|
|
26
|
+
}).catch(function(e) { console.warn('City modules failed:', e); });
|
|
27
|
+
return _cityMods;
|
|
28
|
+
}
|
|
29
|
+
function isDriving() { return _cityMods && _cityMods.vehicle && _cityMods.vehicle.isDriving(); }
|
|
30
|
+
function isConnected() { return false; }
|
|
31
|
+
|
|
32
|
+
// Expose createCharacter + resolveAppearance for the character designer (Phase 3)
|
|
33
|
+
export { createCharacter } from './character.js';
|
|
34
|
+
export { resolveAppearance } from './appearance.js';
|
|
35
|
+
export { buildHair } from './hair.js';
|
|
36
|
+
export { buildFaceSprite } from './face.js';
|
|
37
|
+
export { buildGlasses, buildHeadwear, buildNeckwear } from './accessories.js';
|
|
38
|
+
export { buildOutfit, removeOutfit } from './outfits.js';
|
|
39
|
+
|
|
40
|
+
// ===================== RAYCASTER + COMMAND MENU =====================
|
|
41
|
+
var raycaster = new THREE.Raycaster();
|
|
42
|
+
var mouse = new THREE.Vector2();
|
|
43
|
+
var activeMenu = null; // { agentName, css2dObj, div, timeout }
|
|
44
|
+
var clickHandlerBound = null;
|
|
45
|
+
|
|
46
|
+
function setupClickHandler() {
|
|
47
|
+
if (clickHandlerBound) return;
|
|
48
|
+
// Track mouse-down position to distinguish clicks from camera drags
|
|
49
|
+
var downPos = { x: 0, y: 0 };
|
|
50
|
+
S.renderer.domElement.addEventListener('mousedown', function(e) {
|
|
51
|
+
downPos.x = e.clientX; downPos.y = e.clientY;
|
|
52
|
+
});
|
|
53
|
+
clickHandlerBound = function(event) {
|
|
54
|
+
if (!S.running || !S.renderer) return;
|
|
55
|
+
// Ignore if mouse moved more than 5px (it was a drag, not a click)
|
|
56
|
+
var dx = event.clientX - downPos.x, dy = event.clientY - downPos.y;
|
|
57
|
+
if (Math.sqrt(dx * dx + dy * dy) > 5) return;
|
|
58
|
+
|
|
59
|
+
var rect = S.renderer.domElement.getBoundingClientRect();
|
|
60
|
+
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
61
|
+
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
62
|
+
|
|
63
|
+
raycaster.setFromCamera(mouse, S.camera);
|
|
64
|
+
|
|
65
|
+
// Test all agent body meshes
|
|
66
|
+
var agentMeshes = [];
|
|
67
|
+
for (var name in S.agents3d) {
|
|
68
|
+
var agent = S.agents3d[name];
|
|
69
|
+
if (agent.dying) continue;
|
|
70
|
+
// Collect body-part meshes for intersection
|
|
71
|
+
agent.parts.group.traverse(function(child) {
|
|
72
|
+
if (child.isMesh && !child.userData.isShadow) {
|
|
73
|
+
child.userData._agentName = name;
|
|
74
|
+
agentMeshes.push(child);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var intersects = raycaster.intersectObjects(agentMeshes, false);
|
|
80
|
+
if (intersects.length > 0) {
|
|
81
|
+
var hitAgent = intersects[0].object.userData._agentName;
|
|
82
|
+
if (hitAgent && S.agents3d[hitAgent]) {
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
showCommandMenu(hitAgent);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check monitor screen clicks
|
|
90
|
+
var monitorMeshes = [];
|
|
91
|
+
for (var di = 0; di < S.deskMeshes.length; di++) {
|
|
92
|
+
if (S.deskMeshes[di] && S.deskMeshes[di].screen) {
|
|
93
|
+
S.deskMeshes[di].screen.userData._deskIdx = di;
|
|
94
|
+
monitorMeshes.push(S.deskMeshes[di].screen);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
var monitorHits = raycaster.intersectObjects(monitorMeshes, false);
|
|
98
|
+
if (monitorHits.length > 0) {
|
|
99
|
+
var deskIdx = monitorHits[0].object.userData._deskIdx;
|
|
100
|
+
// Find which agent sits at this desk
|
|
101
|
+
var monitorAgent = null;
|
|
102
|
+
for (var mname in S.agents3d) {
|
|
103
|
+
if (S.agents3d[mname].deskIndex === deskIdx) { monitorAgent = mname; break; }
|
|
104
|
+
}
|
|
105
|
+
if (monitorAgent) {
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
showMonitorPanel(monitorAgent);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Clicked nothing — dismiss menu
|
|
113
|
+
dismissCommandMenu();
|
|
114
|
+
};
|
|
115
|
+
S.renderer.domElement.addEventListener('click', clickHandlerBound);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function showCommandMenu(agentName) {
|
|
119
|
+
dismissCommandMenu();
|
|
120
|
+
var agent = S.agents3d[agentName];
|
|
121
|
+
if (!agent) return;
|
|
122
|
+
|
|
123
|
+
var loc = agent.location || 'desk';
|
|
124
|
+
var isWalking = agent.target !== null;
|
|
125
|
+
|
|
126
|
+
var div = document.createElement('div');
|
|
127
|
+
div.className = 'office3d-cmd-menu';
|
|
128
|
+
|
|
129
|
+
var commands = [
|
|
130
|
+
{ icon: '\uD83D\uDCAC', label: 'Send Message', action: 'send_message', disabled: false },
|
|
131
|
+
{ icon: '\uD83D\uDCCB', label: 'Assign Task', action: 'assign_task', disabled: false },
|
|
132
|
+
{ icon: '\uD83D\uDCE8', label: 'View Messages', action: 'view_messages', disabled: false },
|
|
133
|
+
{ icon: '\uD83D\uDC4B', label: 'Nudge', action: 'nudge', disabled: false },
|
|
134
|
+
{ divider: true },
|
|
135
|
+
{ icon: '\uD83D\uDC57', label: 'Dressing Room', action: 'dressing_room', disabled: loc === 'dressing_room' || isWalking },
|
|
136
|
+
{ icon: '\uD83D\uDCA4', label: 'Go Rest', action: 'rest', disabled: loc === 'rest' || isWalking },
|
|
137
|
+
{ icon: '\uD83D\uDCBB', label: 'Back to Work', action: 'desk', disabled: loc === 'desk' || isWalking },
|
|
138
|
+
{ divider: true },
|
|
139
|
+
{ icon: '\u270F\uFE0F', label: 'Edit Profile', action: 'edit_profile', disabled: false },
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
commands.forEach(function(cmd) {
|
|
143
|
+
if (cmd.divider) {
|
|
144
|
+
var d = document.createElement('div');
|
|
145
|
+
d.className = 'office3d-cmd-divider';
|
|
146
|
+
div.appendChild(d);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
var btn = document.createElement('button');
|
|
150
|
+
btn.className = 'office3d-cmd-btn' + (cmd.disabled ? ' disabled' : '');
|
|
151
|
+
btn.innerHTML = '<span class="office3d-cmd-icon">' + cmd.icon + '</span>' + cmd.label;
|
|
152
|
+
btn.addEventListener('click', function(e) {
|
|
153
|
+
e.stopPropagation();
|
|
154
|
+
dismissCommandMenu();
|
|
155
|
+
executeCommand(agentName, cmd.action);
|
|
156
|
+
});
|
|
157
|
+
div.appendChild(btn);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
var menuObj = new CSS2DObject(div);
|
|
161
|
+
menuObj.position.set(0, 2.1, 0);
|
|
162
|
+
agent.parts.group.add(menuObj);
|
|
163
|
+
|
|
164
|
+
activeMenu = {
|
|
165
|
+
agentName: agentName,
|
|
166
|
+
css2dObj: menuObj,
|
|
167
|
+
div: div,
|
|
168
|
+
timeout: setTimeout(dismissCommandMenu, 5000),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function dismissCommandMenu() {
|
|
173
|
+
if (!activeMenu) return;
|
|
174
|
+
var agent = S.agents3d[activeMenu.agentName];
|
|
175
|
+
if (agent) {
|
|
176
|
+
agent.parts.group.remove(activeMenu.css2dObj);
|
|
177
|
+
}
|
|
178
|
+
if (activeMenu.div.parentElement) activeMenu.div.remove();
|
|
179
|
+
clearTimeout(activeMenu.timeout);
|
|
180
|
+
activeMenu = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
var activeMonitorPanel = null;
|
|
184
|
+
|
|
185
|
+
function showMonitorPanel(agentName) {
|
|
186
|
+
dismissMonitorPanel();
|
|
187
|
+
var history = window.cachedHistory || [];
|
|
188
|
+
var agentMsgs = history.filter(function(m) { return m.from === agentName || m.to === agentName; }).slice(-10);
|
|
189
|
+
|
|
190
|
+
var panel = document.createElement('div');
|
|
191
|
+
panel.className = 'office3d-monitor-panel';
|
|
192
|
+
panel.style.cssText = 'position:fixed;right:20px;top:80px;width:360px;max-height:500px;background:#0c1021;border:1px solid #1a1f36;border-radius:10px;overflow:hidden;z-index:300;box-shadow:0 8px 32px rgba(0,0,0,0.5);font-family:monospace';
|
|
193
|
+
|
|
194
|
+
var header = '<div style="background:#1a1f36;padding:8px 12px;display:flex;align-items:center;justify-content:space-between"><span style="color:#8892b0;font-size:12px">' + agentName + ' \u2014 messages</span><button onclick="this.parentElement.parentElement.remove()" style="background:none;border:none;color:#ff5f57;cursor:pointer;font-size:14px">\u2715</button></div>';
|
|
195
|
+
|
|
196
|
+
var body = '<div style="padding:10px;overflow-y:auto;max-height:440px;font-size:11px">';
|
|
197
|
+
if (!agentMsgs.length) {
|
|
198
|
+
body += '<div style="color:#546178;text-align:center;padding:20px">No messages yet</div>';
|
|
199
|
+
}
|
|
200
|
+
for (var i = 0; i < agentMsgs.length; i++) {
|
|
201
|
+
var m = agentMsgs[i];
|
|
202
|
+
var isFrom = m.from === agentName;
|
|
203
|
+
var color = isFrom ? '#58a6ff' : '#3fb950';
|
|
204
|
+
var content = (m.content || '').substring(0, 150);
|
|
205
|
+
body += '<div style="margin-bottom:8px;padding:6px 8px;background:#111827;border-radius:6px;border-left:2px solid ' + color + '">' +
|
|
206
|
+
'<div style="color:' + color + ';font-size:10px;font-weight:bold;margin-bottom:2px">' + m.from + ' \u2192 ' + m.to + '</div>' +
|
|
207
|
+
'<div style="color:#e6edf3;line-height:1.4">' + content.replace(/</g, '<').replace(/>/g, '>') + (m.content && m.content.length > 150 ? '...' : '') + '</div>' +
|
|
208
|
+
'</div>';
|
|
209
|
+
}
|
|
210
|
+
body += '</div>';
|
|
211
|
+
|
|
212
|
+
panel.innerHTML = header + body;
|
|
213
|
+
document.body.appendChild(panel);
|
|
214
|
+
activeMonitorPanel = panel;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function dismissMonitorPanel() {
|
|
218
|
+
if (activeMonitorPanel) {
|
|
219
|
+
activeMonitorPanel.remove();
|
|
220
|
+
activeMonitorPanel = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Non-blocking input overlay — replaces browser prompt() to avoid freezing events
|
|
225
|
+
var _activeInputOverlay = null;
|
|
226
|
+
function showInputOverlay(label, placeholder, callback) {
|
|
227
|
+
if (_activeInputOverlay) _activeInputOverlay.remove();
|
|
228
|
+
var overlay = document.createElement('div');
|
|
229
|
+
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:10000;display:flex;align-items:center;justify-content:center';
|
|
230
|
+
var box = document.createElement('div');
|
|
231
|
+
box.style.cssText = 'background:#161b22;border:1px solid #30363d;border-radius:12px;padding:20px;min-width:340px;box-shadow:0 8px 32px rgba(0,0,0,0.5)';
|
|
232
|
+
box.innerHTML = '<div style="color:#e6edf3;font-size:13px;font-weight:600;margin-bottom:10px">' + label.replace(/</g, '<') + '</div>';
|
|
233
|
+
var input = document.createElement('input');
|
|
234
|
+
input.type = 'text';
|
|
235
|
+
input.placeholder = placeholder || '';
|
|
236
|
+
input.style.cssText = 'width:100%;padding:8px 12px;background:#0d1117;border:1px solid #30363d;border-radius:8px;color:#e6edf3;font-size:13px;outline:none;box-sizing:border-box;font-family:inherit';
|
|
237
|
+
box.appendChild(input);
|
|
238
|
+
var btns = document.createElement('div');
|
|
239
|
+
btns.style.cssText = 'display:flex;gap:8px;margin-top:12px;justify-content:flex-end';
|
|
240
|
+
var cancelBtn = document.createElement('button');
|
|
241
|
+
cancelBtn.textContent = 'Cancel';
|
|
242
|
+
cancelBtn.style.cssText = 'padding:6px 14px;background:#21262d;border:1px solid #30363d;border-radius:6px;color:#8b949e;font-size:12px;cursor:pointer;font-family:inherit';
|
|
243
|
+
var submitBtn = document.createElement('button');
|
|
244
|
+
submitBtn.textContent = 'Send';
|
|
245
|
+
submitBtn.style.cssText = 'padding:6px 14px;background:#238636;border:1px solid #2ea043;border-radius:6px;color:#fff;font-size:12px;cursor:pointer;font-weight:600;font-family:inherit';
|
|
246
|
+
btns.appendChild(cancelBtn);
|
|
247
|
+
btns.appendChild(submitBtn);
|
|
248
|
+
box.appendChild(btns);
|
|
249
|
+
overlay.appendChild(box);
|
|
250
|
+
document.body.appendChild(overlay);
|
|
251
|
+
_activeInputOverlay = overlay;
|
|
252
|
+
input.focus();
|
|
253
|
+
function close() { overlay.remove(); _activeInputOverlay = null; }
|
|
254
|
+
function submit() { var val = input.value; close(); callback(val); }
|
|
255
|
+
submitBtn.addEventListener('click', submit);
|
|
256
|
+
cancelBtn.addEventListener('click', close);
|
|
257
|
+
overlay.addEventListener('click', function(e) { if (e.target === overlay) close(); });
|
|
258
|
+
input.addEventListener('keydown', function(e) {
|
|
259
|
+
if (e.key === 'Enter') { e.preventDefault(); submit(); }
|
|
260
|
+
if (e.key === 'Escape') { e.preventDefault(); close(); }
|
|
261
|
+
e.stopPropagation(); // prevent WASD from moving player while typing
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function executeCommand(agentName, action) {
|
|
266
|
+
var agent = S.agents3d[agentName];
|
|
267
|
+
if (!agent) return;
|
|
268
|
+
|
|
269
|
+
switch (action) {
|
|
270
|
+
case 'dressing_room':
|
|
271
|
+
agent.location = 'walking';
|
|
272
|
+
agent.isSitting = false;
|
|
273
|
+
showBubble(agent, 'Going to change!');
|
|
274
|
+
navigateTo(agent, DRESSING_ROOM_ENTRANCE.x, DRESSING_ROOM_ENTRANCE.z, function() {
|
|
275
|
+
navigateTo(agent, DRESSING_ROOM_POS.x, DRESSING_ROOM_POS.z, function() {
|
|
276
|
+
agent.location = 'dressing_room';
|
|
277
|
+
agent.isSitting = false;
|
|
278
|
+
showBubble(agent, 'Time for a new look!');
|
|
279
|
+
// Open character editor for this agent
|
|
280
|
+
window.editingAgent = agentName;
|
|
281
|
+
if (typeof window.openProfileEditor === 'function') {
|
|
282
|
+
window.openProfileEditor();
|
|
283
|
+
}
|
|
284
|
+
// Listen for editor close to return to desk
|
|
285
|
+
waitForEditorClose(agent);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case 'rest':
|
|
291
|
+
agent.location = 'walking';
|
|
292
|
+
agent.isSitting = false;
|
|
293
|
+
showBubble(agent, 'Need a break...');
|
|
294
|
+
navigateTo(agent, REST_AREA_ENTRANCE.x, REST_AREA_ENTRANCE.z, function() {
|
|
295
|
+
navigateTo(agent, REST_AREA_POS.x, REST_AREA_POS.z, function() {
|
|
296
|
+
agent.location = 'rest';
|
|
297
|
+
agent.state = 'sleeping';
|
|
298
|
+
agent.isSitting = false;
|
|
299
|
+
showBubble(agent, 'Zzz...');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
break;
|
|
303
|
+
|
|
304
|
+
case 'desk':
|
|
305
|
+
agent.location = 'walking';
|
|
306
|
+
agent.state = 'active';
|
|
307
|
+
agent.isSitting = false;
|
|
308
|
+
showBubble(agent, 'Back to work!');
|
|
309
|
+
navigateTo(agent, agent.deskPos.x, agent.deskPos.z + 0.7, function() {
|
|
310
|
+
agent.location = 'desk';
|
|
311
|
+
agent.registered = true;
|
|
312
|
+
});
|
|
313
|
+
break;
|
|
314
|
+
|
|
315
|
+
case 'send_message':
|
|
316
|
+
showInputOverlay('Send message to ' + agentName + ':', 'Type your message...', function(msg) {
|
|
317
|
+
if (msg && msg.trim()) {
|
|
318
|
+
showBubble(agent, 'Message incoming...');
|
|
319
|
+
fetch('/api/inject' + (window.activeProject ? '?project=' + encodeURIComponent(window.activeProject) : ''), {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
|
|
322
|
+
body: JSON.stringify({ to: agentName, content: msg.trim() })
|
|
323
|
+
}).then(function() { showBubble(agent, 'Got it!'); });
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
break;
|
|
327
|
+
|
|
328
|
+
case 'assign_task':
|
|
329
|
+
showInputOverlay('New task for ' + agentName + ':', 'Task title...', function(title) {
|
|
330
|
+
if (title && title.trim()) {
|
|
331
|
+
showBubble(agent, 'New task assigned!');
|
|
332
|
+
fetch('/api/tasks' + (window.activeProject ? '?project=' + encodeURIComponent(window.activeProject) : ''), {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
|
|
335
|
+
body: JSON.stringify({ title: title.trim(), assignee: agentName, status: 'pending' })
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
break;
|
|
340
|
+
|
|
341
|
+
case 'view_messages':
|
|
342
|
+
showBubble(agent, 'Showing messages...');
|
|
343
|
+
if (typeof window.switchView === 'function') window.switchView('messages');
|
|
344
|
+
var searchInput = document.getElementById('search-input');
|
|
345
|
+
if (searchInput) {
|
|
346
|
+
searchInput.value = agentName;
|
|
347
|
+
if (typeof window.onSearch === 'function') window.onSearch();
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
|
|
351
|
+
case 'nudge':
|
|
352
|
+
showBubble(agent, 'Hey! Wake up!');
|
|
353
|
+
fetch('/api/inject' + (window.activeProject ? '?project=' + encodeURIComponent(window.activeProject) : ''), {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
|
|
356
|
+
body: JSON.stringify({ to: agentName, content: 'Hey ' + agentName + ', the user is waiting for you. Please check for new messages and continue your work.' })
|
|
357
|
+
});
|
|
358
|
+
break;
|
|
359
|
+
|
|
360
|
+
case 'edit_profile':
|
|
361
|
+
window.editingAgent = agentName;
|
|
362
|
+
if (typeof window.openProfileEditor === 'function') {
|
|
363
|
+
window.openProfileEditor();
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function waitForEditorClose(agent) {
|
|
370
|
+
// Poll for the character designer panel closing
|
|
371
|
+
var checkInterval = setInterval(function() {
|
|
372
|
+
var panel = document.getElementById('char-designer');
|
|
373
|
+
if (!panel || !panel.classList.contains('open')) {
|
|
374
|
+
clearInterval(checkInterval);
|
|
375
|
+
// Agent walks back to desk after editor closes
|
|
376
|
+
if (agent.location === 'dressing_room') {
|
|
377
|
+
agent.location = 'walking';
|
|
378
|
+
showBubble(agent, 'Looking good!');
|
|
379
|
+
navigateTo(agent, DRESSING_ROOM_ENTRANCE.x, DRESSING_ROOM_ENTRANCE.z, function() {
|
|
380
|
+
navigateTo(agent, agent.deskPos.x, agent.deskPos.z + 0.7, function() {
|
|
381
|
+
agent.location = 'desk';
|
|
382
|
+
agent.registered = true;
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}, 500);
|
|
388
|
+
// Safety: stop checking after 2 minutes
|
|
389
|
+
setTimeout(function() { clearInterval(checkInterval); }, 120000);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ===================== ANIMATION LOOP =====================
|
|
393
|
+
function animate() {
|
|
394
|
+
if (!S.running) return;
|
|
395
|
+
S.animationId = requestAnimationFrame(animate);
|
|
396
|
+
|
|
397
|
+
var dt = Math.min(S.clock.getDelta(), 0.1);
|
|
398
|
+
var time = S.clock.getElapsedTime();
|
|
399
|
+
|
|
400
|
+
for (var name in S.agents3d) {
|
|
401
|
+
updateAgent(S.agents3d[name], dt, time);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Player avatar mode — skip when driving (vehicle takes over)
|
|
405
|
+
if (isPlayerMode() && S.controls && S.controls.keys && !isDriving()) {
|
|
406
|
+
updatePlayer(dt, time, S.controls.keys);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Vehicle driving mode (city environment)
|
|
410
|
+
if (isDriving() && _cityMods && _cityMods.vehicle) {
|
|
411
|
+
_cityMods.vehicle.updateVehicle(dt);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// City environment updates (only if modules loaded)
|
|
415
|
+
if (S.currentEnv === 'city' && _cityMods && _cityMods.loaded) {
|
|
416
|
+
if (_cityMods.economy) _cityMods.economy.updateEconomyUI(dt);
|
|
417
|
+
if (_cityMods.daynight) _cityMods.daynight.updateDayNight(dt);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// City NPC animation (pedestrians, cars, traffic lights)
|
|
421
|
+
if (S.currentEnv === 'city' && S._updateCity) {
|
|
422
|
+
S._updateCity(dt);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Hide roof when camera is above ceiling height
|
|
426
|
+
if (S._roofGroup) {
|
|
427
|
+
S._roofGroup.visible = S.camera.position.y < 6.5;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Manager office door animation — opens when agent is near the door
|
|
431
|
+
if (S._managerDoor && S._managerOfficePos) {
|
|
432
|
+
var doorX = S._managerOfficePos.x;
|
|
433
|
+
var doorZ = S._managerOfficePos.z - 3.5; // front of office
|
|
434
|
+
var shouldOpen = false;
|
|
435
|
+
for (var an in S.agents3d) {
|
|
436
|
+
var ag = S.agents3d[an];
|
|
437
|
+
if (ag.target || ag.location === 'walking') {
|
|
438
|
+
var adx = ag.pos.x - doorX;
|
|
439
|
+
var adz = ag.pos.z - doorZ;
|
|
440
|
+
if (Math.sqrt(adx * adx + adz * adz) < 3) { shouldOpen = true; break; }
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// Also open for player
|
|
444
|
+
if (!shouldOpen && S._player) {
|
|
445
|
+
var pdx = S._player.pos.x - doorX;
|
|
446
|
+
var pdz = S._player.pos.z - doorZ;
|
|
447
|
+
if (Math.sqrt(pdx * pdx + pdz * pdz) < 3) shouldOpen = true;
|
|
448
|
+
}
|
|
449
|
+
S._managerDoorOpen = shouldOpen ? 1 : 0;
|
|
450
|
+
S._managerDoorLerp += (S._managerDoorOpen - S._managerDoorLerp) * Math.min(1, dt * 4);
|
|
451
|
+
S._managerDoor.position.x = S._managerDoorLerp * 1.3; // slide open to the right
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Update TV screen every ~0.5s for smooth ticker
|
|
455
|
+
if (!S._tvTimer) S._tvTimer = 0;
|
|
456
|
+
S._tvTimer += dt;
|
|
457
|
+
if (S._tvTimer >= 0.15) {
|
|
458
|
+
S._tvTimer = 0;
|
|
459
|
+
updateTVScreen(time);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Jukebox neon color cycling
|
|
463
|
+
if (S._jukebox && S._jukebox.neonMat) {
|
|
464
|
+
if (!S._jukeboxTimer) S._jukeboxTimer = 0;
|
|
465
|
+
S._jukeboxTimer += dt;
|
|
466
|
+
if (S._jukeboxTimer >= 1.5) { // cycle every 1.5s
|
|
467
|
+
S._jukeboxTimer = 0;
|
|
468
|
+
S._jukebox.neonIndex = (S._jukebox.neonIndex + 1) % S._jukebox.neonColors.length;
|
|
469
|
+
var c = S._jukebox.neonColors[S._jukebox.neonIndex];
|
|
470
|
+
S._jukebox.neonMat.color.setHex(c);
|
|
471
|
+
S._jukebox.neonMat.emissive.setHex(c);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
S.controls.update(dt);
|
|
476
|
+
S.renderer.render(S.scene, S.camera);
|
|
477
|
+
S.cssRenderer.render(S.scene, S.camera);
|
|
478
|
+
|
|
479
|
+
// Minimap update (every ~0.3s)
|
|
480
|
+
if (!S._minimapTimer) S._minimapTimer = 0;
|
|
481
|
+
S._minimapTimer += dt;
|
|
482
|
+
if (S._minimapTimer >= 0.3) {
|
|
483
|
+
S._minimapTimer = 0;
|
|
484
|
+
updateMinimap();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// FPS
|
|
488
|
+
S.fpsCounter++;
|
|
489
|
+
var now = performance.now();
|
|
490
|
+
if (now - S.fpsTime > 1000) {
|
|
491
|
+
var fpsEl = document.getElementById('office-fps');
|
|
492
|
+
if (fpsEl && window.activeView === 'office') fpsEl.textContent = S.fpsCounter + ' fps';
|
|
493
|
+
S.fpsCounter = 0;
|
|
494
|
+
S.fpsTime = now;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ===================== FULLSCREEN TOGGLE =====================
|
|
499
|
+
var _fullscreenBtn = null;
|
|
500
|
+
|
|
501
|
+
function createFullscreenButton() {
|
|
502
|
+
if (_fullscreenBtn) return;
|
|
503
|
+
_fullscreenBtn = document.createElement('button');
|
|
504
|
+
_fullscreenBtn.id = 'office-fullscreen-btn';
|
|
505
|
+
_fullscreenBtn.innerHTML = '⛶'; // ⛶ fullscreen icon
|
|
506
|
+
_fullscreenBtn.title = 'Enter Fullscreen (End key to exit)';
|
|
507
|
+
// Custom button hidden — dashboard's own Fullscreen button now handles 3D-only fullscreen
|
|
508
|
+
_fullscreenBtn.style.cssText = 'display:none;';
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function removeFullscreenButton() {
|
|
512
|
+
if (_fullscreenBtn) {
|
|
513
|
+
_fullscreenBtn.style.display = 'none';
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function toggleFullscreen() {
|
|
518
|
+
if (document.fullscreenElement) {
|
|
519
|
+
document.exitFullscreen();
|
|
520
|
+
} else {
|
|
521
|
+
// Fullscreen only the 3D Hub container — not the entire dashboard
|
|
522
|
+
var target = S.container || document.getElementById('office-3d-container');
|
|
523
|
+
if (target) {
|
|
524
|
+
target.requestFullscreen().catch(function() {});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// "End" key exits fullscreen
|
|
530
|
+
document.addEventListener('keydown', function(e) {
|
|
531
|
+
if (e.code === 'End' && document.fullscreenElement) {
|
|
532
|
+
document.exitFullscreen();
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// Update button icon + minimap visibility on fullscreen change
|
|
537
|
+
document.addEventListener('fullscreenchange', function() {
|
|
538
|
+
if (!_fullscreenBtn) return;
|
|
539
|
+
if (document.fullscreenElement) {
|
|
540
|
+
_fullscreenBtn.innerHTML = '✖'; // ✖ exit icon
|
|
541
|
+
_fullscreenBtn.title = 'Exit Fullscreen (End key)';
|
|
542
|
+
// Show minimap in fullscreen only
|
|
543
|
+
if (_minimapContainer) _minimapContainer.style.display = 'block';
|
|
544
|
+
} else {
|
|
545
|
+
_fullscreenBtn.innerHTML = '⛶'; // ⛶ fullscreen icon
|
|
546
|
+
_fullscreenBtn.title = 'Enter Fullscreen (End key to exit)';
|
|
547
|
+
// Hide minimap when exiting fullscreen
|
|
548
|
+
if (_minimapContainer) _minimapContainer.style.display = 'none';
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// ===================== MINIMAP =====================
|
|
553
|
+
var _minimapCanvas = null;
|
|
554
|
+
var _minimapCtx = null;
|
|
555
|
+
var _minimapContainer = null;
|
|
556
|
+
|
|
557
|
+
function createMinimap() {
|
|
558
|
+
if (_minimapContainer) return;
|
|
559
|
+
_minimapContainer = document.createElement('div');
|
|
560
|
+
_minimapContainer.id = 'office-minimap';
|
|
561
|
+
// Minimap only visible in fullscreen mode
|
|
562
|
+
_minimapContainer.style.cssText = 'position:fixed;bottom:12px;left:12px;z-index:10001;width:140px;height:140px;border-radius:8px;border:1px solid #30363d;overflow:hidden;background:rgba(0,0,0,0.75);pointer-events:none;display:none;';
|
|
563
|
+
|
|
564
|
+
_minimapCanvas = document.createElement('canvas');
|
|
565
|
+
_minimapCanvas.width = 140;
|
|
566
|
+
_minimapCanvas.height = 140;
|
|
567
|
+
_minimapContainer.appendChild(_minimapCanvas);
|
|
568
|
+
_minimapCtx = _minimapCanvas.getContext('2d');
|
|
569
|
+
|
|
570
|
+
document.body.appendChild(_minimapContainer);
|
|
571
|
+
// Minimap starts hidden — only shown in fullscreen mode
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function removeMinimap() {
|
|
575
|
+
if (_minimapContainer) {
|
|
576
|
+
_minimapContainer.style.display = 'none';
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function updateMinimap() {
|
|
581
|
+
if (!_minimapCtx || !S.running) return;
|
|
582
|
+
var ctx = _minimapCtx;
|
|
583
|
+
var W = 140, H = 140;
|
|
584
|
+
// Map world coords to minimap: world is roughly -14..14 X, -8..8 Z
|
|
585
|
+
var scaleX = W / 32; // 32 world units width
|
|
586
|
+
var scaleZ = H / 20; // 20 world units depth
|
|
587
|
+
var offX = 16; // center offset
|
|
588
|
+
var offZ = 10;
|
|
589
|
+
|
|
590
|
+
ctx.clearRect(0, 0, W, H);
|
|
591
|
+
|
|
592
|
+
// Draw floor outline
|
|
593
|
+
ctx.strokeStyle = '#30363d';
|
|
594
|
+
ctx.lineWidth = 1;
|
|
595
|
+
ctx.strokeRect(2, 2, W - 4, H - 4);
|
|
596
|
+
|
|
597
|
+
// Draw desk positions as gray squares
|
|
598
|
+
var allDesks = S._campusDeskPositions || [];
|
|
599
|
+
ctx.fillStyle = 'rgba(100,100,120,0.4)';
|
|
600
|
+
for (var di = 0; di < allDesks.length; di++) {
|
|
601
|
+
var dp = allDesks[di];
|
|
602
|
+
var dx = (dp.x + offX) * scaleX;
|
|
603
|
+
var dz = (dp.z + offZ) * scaleZ;
|
|
604
|
+
ctx.fillRect(dx - 3, dz - 2, 6, 4);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Draw agents as colored dots
|
|
608
|
+
for (var name in S.agents3d) {
|
|
609
|
+
var agent = S.agents3d[name];
|
|
610
|
+
if (!agent.registered || agent.dying) continue;
|
|
611
|
+
var ax = (agent.pos.x + offX) * scaleX;
|
|
612
|
+
var az = (agent.pos.z + offZ) * scaleZ;
|
|
613
|
+
|
|
614
|
+
// Color by state
|
|
615
|
+
if (agent.state === 'active' && agent.isListening) {
|
|
616
|
+
ctx.fillStyle = '#4ade80'; // green - listening
|
|
617
|
+
} else if (agent.state === 'active') {
|
|
618
|
+
ctx.fillStyle = '#58a6ff'; // blue - active
|
|
619
|
+
} else if (agent.state === 'sleeping') {
|
|
620
|
+
ctx.fillStyle = '#facc15'; // yellow - sleeping
|
|
621
|
+
} else {
|
|
622
|
+
ctx.fillStyle = '#f87171'; // red - dead
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
ctx.beginPath();
|
|
626
|
+
ctx.arc(ax, az, 4, 0, Math.PI * 2);
|
|
627
|
+
ctx.fill();
|
|
628
|
+
|
|
629
|
+
// Agent name label (tiny)
|
|
630
|
+
ctx.fillStyle = 'rgba(255,255,255,0.7)';
|
|
631
|
+
ctx.font = '7px sans-serif';
|
|
632
|
+
ctx.textAlign = 'center';
|
|
633
|
+
ctx.fillText(agent.displayName.substring(0, 6), ax, az - 6);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Draw player as white triangle
|
|
637
|
+
if (S._player && isPlayerMode()) {
|
|
638
|
+
var px = (S._player.pos.x + offX) * scaleX;
|
|
639
|
+
var pz = (S._player.pos.z + offZ) * scaleZ;
|
|
640
|
+
ctx.fillStyle = '#ffffff';
|
|
641
|
+
ctx.beginPath();
|
|
642
|
+
ctx.moveTo(px, pz - 5);
|
|
643
|
+
ctx.lineTo(px - 3.5, pz + 3);
|
|
644
|
+
ctx.lineTo(px + 3.5, pz + 3);
|
|
645
|
+
ctx.closePath();
|
|
646
|
+
ctx.fill();
|
|
647
|
+
ctx.fillStyle = 'rgba(255,255,255,0.8)';
|
|
648
|
+
ctx.font = '7px sans-serif';
|
|
649
|
+
ctx.textAlign = 'center';
|
|
650
|
+
ctx.fillText('You', px, pz - 7);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// ===================== CONTROLS HUD (H key) =====================
|
|
655
|
+
var _controlsHud = null;
|
|
656
|
+
|
|
657
|
+
function createControlsHud() {
|
|
658
|
+
if (_controlsHud) return;
|
|
659
|
+
_controlsHud = document.createElement('div');
|
|
660
|
+
_controlsHud.id = 'office-controls-hud';
|
|
661
|
+
_controlsHud.style.cssText = 'position:fixed;bottom:16px;left:16px;z-index:1001;background:rgba(0,0,0,0.75);color:#c9d1d9;padding:14px 18px;border-radius:10px;font-size:12px;line-height:1.8;pointer-events:none;border:1px solid rgba(48,54,61,0.6);backdrop-filter:blur(4px);display:none;font-family:monospace;';
|
|
662
|
+
_controlsHud.innerHTML =
|
|
663
|
+
'<div style="color:#58a6ff;font-weight:600;margin-bottom:4px;font-size:13px">Controls</div>' +
|
|
664
|
+
'<div><span style="color:#7ee787">W A S D</span> Move</div>' +
|
|
665
|
+
'<div><span style="color:#7ee787">Space</span> Jump</div>' +
|
|
666
|
+
'<div><span style="color:#7ee787">Shift</span> Sprint</div>' +
|
|
667
|
+
'<div><span style="color:#7ee787">E</span> Sit / Stand</div>' +
|
|
668
|
+
'<div><span style="color:#7ee787">Mouse</span> Look around</div>' +
|
|
669
|
+
'<div><span style="color:#7ee787">Scroll</span> Zoom in/out</div>' +
|
|
670
|
+
'<div><span style="color:#7ee787">Click</span> Agent commands</div>' +
|
|
671
|
+
'<div style="margin-top:6px;border-top:1px solid #30363d;padding-top:6px">' +
|
|
672
|
+
'<span style="color:#d2a8ff">H</span> Toggle this HUD</div>' +
|
|
673
|
+
'<div><span style="color:#d2a8ff">End</span> Exit fullscreen</div>';
|
|
674
|
+
document.body.appendChild(_controlsHud);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function removeControlsHud() {
|
|
678
|
+
if (_controlsHud && _controlsHud.parentElement) {
|
|
679
|
+
_controlsHud.remove();
|
|
680
|
+
_controlsHud = null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Toggle HUD with H key (only when 3D is running and not typing in an input)
|
|
685
|
+
document.addEventListener('keydown', function(e) {
|
|
686
|
+
if (e.code === 'KeyH' && S.running && !e.target.matches('input,textarea')) {
|
|
687
|
+
if (_controlsHud) {
|
|
688
|
+
_controlsHud.style.display = _controlsHud.style.display === 'none' ? 'block' : 'none';
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// World Builder (B key) — lazy-loaded to avoid breaking 3D Hub if builder has issues
|
|
692
|
+
if (e.code === 'KeyB' && S.running && !e.target.matches('input,textarea') && isPlayerMode() && window._builderModule) {
|
|
693
|
+
window._builderModule.toggleBuilder();
|
|
694
|
+
}
|
|
695
|
+
// Vehicle enter/exit (E key) — city environment only
|
|
696
|
+
if (e.code === 'KeyE' && S.running && !e.target.matches('input,textarea') && S.currentEnv === 'city' && _cityMods && _cityMods.vehicle) {
|
|
697
|
+
if (isDriving()) {
|
|
698
|
+
_cityMods.vehicle.exitVehicle();
|
|
699
|
+
} else if (isPlayerMode() && S._player) {
|
|
700
|
+
var playerPos = S._player.pos || S._player.position || { x: 0, z: 0 };
|
|
701
|
+
var nearest = _cityMods.vehicle.getNearestVehicle(playerPos);
|
|
702
|
+
if (nearest) {
|
|
703
|
+
_cityMods.vehicle.enterVehicle(nearest);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// ===================== PUBLIC API =====================
|
|
710
|
+
window.office3dStart = function() {
|
|
711
|
+
if (S.running) return;
|
|
712
|
+
S.container = document.getElementById('office-3d-container');
|
|
713
|
+
if (!S.container) return;
|
|
714
|
+
|
|
715
|
+
if (!S.scene) {
|
|
716
|
+
if (!initScene()) return;
|
|
717
|
+
buildEnvironment();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (!S.container.contains(S.renderer.domElement)) {
|
|
721
|
+
S.container.appendChild(S.renderer.domElement);
|
|
722
|
+
S.container.appendChild(S.cssRenderer.domElement);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
var w = S.container.clientWidth;
|
|
726
|
+
var h = S.container.clientHeight;
|
|
727
|
+
if (w > 0 && h > 0) {
|
|
728
|
+
S.camera.aspect = w / h;
|
|
729
|
+
S.camera.updateProjectionMatrix();
|
|
730
|
+
S.renderer.setSize(w, h);
|
|
731
|
+
S.cssRenderer.setSize(w, h);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
S.running = true;
|
|
735
|
+
S.clock.start();
|
|
736
|
+
S.lastProcessedMsg = 0;
|
|
737
|
+
syncAgents();
|
|
738
|
+
processMessages();
|
|
739
|
+
setupClickHandler();
|
|
740
|
+
createFullscreenButton();
|
|
741
|
+
createMinimap();
|
|
742
|
+
createControlsHud();
|
|
743
|
+
// Lazy-load World Builder (won't break 3D Hub if builder has issues)
|
|
744
|
+
setTimeout(function() {
|
|
745
|
+
import('./builder.js').then(function(mod) {
|
|
746
|
+
window._builderModule = mod;
|
|
747
|
+
mod.loadSavedWorld();
|
|
748
|
+
}).catch(function(e) {
|
|
749
|
+
console.warn('[builder] Failed to load:', e.message);
|
|
750
|
+
});
|
|
751
|
+
}, 1500);
|
|
752
|
+
animate();
|
|
753
|
+
|
|
754
|
+
if (S.syncInterval) clearInterval(S.syncInterval);
|
|
755
|
+
S.syncInterval = setInterval(function() {
|
|
756
|
+
if (S.running && window.activeView === 'office') {
|
|
757
|
+
syncAgents();
|
|
758
|
+
processMessages();
|
|
759
|
+
updateTVScreen(S.clock.getElapsedTime());
|
|
760
|
+
}
|
|
761
|
+
}, 2000);
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
window.office3dStop = function() {
|
|
765
|
+
S.running = false;
|
|
766
|
+
if (S.animationId) {
|
|
767
|
+
cancelAnimationFrame(S.animationId);
|
|
768
|
+
S.animationId = null;
|
|
769
|
+
}
|
|
770
|
+
if (S.syncInterval) {
|
|
771
|
+
clearInterval(S.syncInterval);
|
|
772
|
+
S.syncInterval = null;
|
|
773
|
+
}
|
|
774
|
+
dismissCommandMenu();
|
|
775
|
+
removeFullscreenButton();
|
|
776
|
+
removeMinimap();
|
|
777
|
+
removeControlsHud();
|
|
778
|
+
// Exit builder mode if active
|
|
779
|
+
if (window._builderModule && window._builderModule.isBuilderActive()) {
|
|
780
|
+
window._builderModule.exitBuilder();
|
|
781
|
+
}
|
|
782
|
+
// Exit fullscreen when leaving 3D Hub
|
|
783
|
+
if (document.fullscreenElement) document.exitFullscreen();
|
|
784
|
+
// Hide "Press E to sit" prompt so it doesn't leak to other tabs
|
|
785
|
+
var sitPrompt = S._player && S._player._sitPrompt;
|
|
786
|
+
if (sitPrompt) sitPrompt.style.display = 'none';
|
|
787
|
+
// Hide iframe overlay if player was sitting at a monitor
|
|
788
|
+
var iframeOverlay = document.getElementById('office-monitor-overlay');
|
|
789
|
+
if (iframeOverlay) iframeOverlay.style.display = 'none';
|
|
790
|
+
// Clean up jukebox overlay + prompt
|
|
791
|
+
dismissJukebox();
|
|
792
|
+
// Hide "Press E for Jukebox" prompt
|
|
793
|
+
var jukeboxPrompt = S._player && S._player._jukeboxPrompt;
|
|
794
|
+
if (jukeboxPrompt) jukeboxPrompt.style.display = 'none';
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
window.office3dSetEnvironment = function(env) {
|
|
798
|
+
if (env === S.currentEnv) return;
|
|
799
|
+
S.currentEnv = env;
|
|
800
|
+
// Load city modules on demand
|
|
801
|
+
if (env === 'city') getCityMods();
|
|
802
|
+
if (S.scene) {
|
|
803
|
+
// Remove all existing agents so they get recreated with proper desk assignments
|
|
804
|
+
for (var name in S.agents3d) {
|
|
805
|
+
var agent = S.agents3d[name];
|
|
806
|
+
S.scene.remove(agent.parts.group);
|
|
807
|
+
agent.parts.group.traverse(function(child) {
|
|
808
|
+
if (child.geometry) child.geometry.dispose();
|
|
809
|
+
if (child.material) {
|
|
810
|
+
if (child.material.map) child.material.map.dispose();
|
|
811
|
+
child.material.dispose();
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
S.agents3d = {};
|
|
816
|
+
S._tvScreen = null;
|
|
817
|
+
S._roofGroup = null;
|
|
818
|
+
S._managerDoor = null;
|
|
819
|
+
S._managerDoorOpen = 0;
|
|
820
|
+
S._managerDoorLerp = 0;
|
|
821
|
+
S._managerOfficePos = null;
|
|
822
|
+
S._campusDeskPositions = null;
|
|
823
|
+
S.lastProcessedMsg = 0;
|
|
824
|
+
invalidateColliders();
|
|
825
|
+
buildEnvironment();
|
|
826
|
+
// syncAgents will recreate all agents with correct desk assignments
|
|
827
|
+
syncAgents();
|
|
828
|
+
processMessages();
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
window.office3dSetCamSpeed = function(speed) {
|
|
833
|
+
if (S.controls) S.controls.moveSpeed = speed;
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// Player avatar API
|
|
837
|
+
window.office3dEnterWorld = function() {
|
|
838
|
+
spawnPlayer();
|
|
839
|
+
// Show controls hint briefly
|
|
840
|
+
if (_controlsHud) {
|
|
841
|
+
_controlsHud.style.display = 'block';
|
|
842
|
+
setTimeout(function() {
|
|
843
|
+
if (_controlsHud) _controlsHud.style.display = 'none';
|
|
844
|
+
}, 4000);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
window.office3dExitWorld = function() {
|
|
848
|
+
despawnPlayer();
|
|
849
|
+
};
|
|
850
|
+
window.office3dIsPlayerMode = function() {
|
|
851
|
+
return isPlayerMode();
|
|
852
|
+
};
|
|
853
|
+
window.office3dSavePlayerAppearance = function(app) {
|
|
854
|
+
savePlayerAppearance(app);
|
|
855
|
+
};
|
|
856
|
+
window.office3dGetPlayerAppearance = function() {
|
|
857
|
+
return getPlayerAppearance();
|
|
858
|
+
};
|
|
859
|
+
window.office3dRebuildPlayer = function(appearance) {
|
|
860
|
+
if (!S._player) return;
|
|
861
|
+
savePlayerAppearance(appearance);
|
|
862
|
+
// Rebuild: despawn and respawn with new appearance
|
|
863
|
+
var pos = { x: S._player.pos.x, z: S._player.pos.z };
|
|
864
|
+
var facing = S._player.facing;
|
|
865
|
+
despawnPlayer();
|
|
866
|
+
var p = spawnPlayer();
|
|
867
|
+
p.pos.x = pos.x;
|
|
868
|
+
p.pos.z = pos.z;
|
|
869
|
+
p.facing = facing;
|
|
870
|
+
p.parts.group.position.set(pos.x, 0, pos.z);
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// Handle visibility change for 3D mode
|
|
874
|
+
document.addEventListener('visibilitychange', function() {
|
|
875
|
+
if (document.hidden && S.running) {
|
|
876
|
+
window.office3dStop();
|
|
877
|
+
} else if (!document.hidden && window.activeView === 'office' && window.officeMode === '3d') {
|
|
878
|
+
window.office3dStart();
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// Auto-start if 3D Hub is already the active view when this module finishes loading
|
|
883
|
+
// (module loads async, so switchView('office') may have already fired before we defined office3dStart)
|
|
884
|
+
if (window.activeView === 'office') {
|
|
885
|
+
window.office3dStart();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// ===================== INTERACTIVE IFRAME MONITOR (Phase 2) =====================
|
|
889
|
+
var activeIframe = null;
|
|
890
|
+
|
|
891
|
+
window.onPlayerSit = function(deskIdx) {
|
|
892
|
+
if (activeIframe) return;
|
|
893
|
+
var container = document.getElementById('office-3d-container') || document.getElementById('office-area');
|
|
894
|
+
if (!container) return;
|
|
895
|
+
|
|
896
|
+
// Create iframe overlay positioned over the 3D canvas
|
|
897
|
+
var overlay = document.createElement('div');
|
|
898
|
+
overlay.id = 'office-iframe-overlay';
|
|
899
|
+
overlay.style.cssText = 'position:absolute;top:5%;left:10%;width:80%;height:85%;z-index:200;background:#000;border-radius:8px;box-shadow:0 0 40px rgba(88,166,255,0.3);overflow:hidden;display:flex;flex-direction:column';
|
|
900
|
+
|
|
901
|
+
// Header bar (mimics monitor bezel)
|
|
902
|
+
var header = document.createElement('div');
|
|
903
|
+
header.style.cssText = 'background:#1a1f36;padding:6px 12px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0';
|
|
904
|
+
header.innerHTML = '<div style="display:flex;gap:6px"><span style="width:10px;height:10px;border-radius:50%;background:#ff5f57"></span><span style="width:10px;height:10px;border-radius:50%;background:#ffbd2e"></span><span style="width:10px;height:10px;border-radius:50%;background:#28c840"></span></div><span style="color:#8892b0;font-size:11px;font-family:monospace">Let Them Talk Dashboard</span><button id="office-leave-btn" style="background:#ff5f57;color:#fff;border:none;border-radius:4px;padding:3px 12px;font-size:11px;font-weight:bold;cursor:pointer;font-family:monospace">LEAVE</button>';
|
|
905
|
+
header.querySelector('#office-leave-btn').addEventListener('click', function() {
|
|
906
|
+
if (typeof window.onPlayerStand === 'function') window.onPlayerStand();
|
|
907
|
+
// Also trigger player stand-up in player.js
|
|
908
|
+
if (typeof window.playerForceStand === 'function') window.playerForceStand();
|
|
909
|
+
});
|
|
910
|
+
overlay.appendChild(header);
|
|
911
|
+
|
|
912
|
+
// Dashboard iframe
|
|
913
|
+
var iframe = document.createElement('iframe');
|
|
914
|
+
iframe.src = window.location.origin || 'http://localhost:3000';
|
|
915
|
+
iframe.style.cssText = 'flex:1;border:none;width:100%;background:#0d1117';
|
|
916
|
+
iframe.allow = 'clipboard-read; clipboard-write';
|
|
917
|
+
overlay.appendChild(iframe);
|
|
918
|
+
|
|
919
|
+
container.style.position = 'relative';
|
|
920
|
+
container.appendChild(overlay);
|
|
921
|
+
activeIframe = overlay;
|
|
922
|
+
|
|
923
|
+
// Focus iframe for keyboard input
|
|
924
|
+
iframe.addEventListener('load', function() { iframe.focus(); });
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
window.onPlayerStand = function() {
|
|
928
|
+
if (activeIframe) {
|
|
929
|
+
activeIframe.remove();
|
|
930
|
+
activeIframe = null;
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// ===================== JUKEBOX INTERACTION =====================
|
|
935
|
+
var _jukeboxOverlay = null;
|
|
936
|
+
var _jukeboxPopup = null; // popup player window reference (module-scoped to survive jukebox UI reopen)
|
|
937
|
+
|
|
938
|
+
window.onJukeboxInteract = function() {
|
|
939
|
+
if (_jukeboxOverlay) return; // already open
|
|
940
|
+
var container = document.getElementById('office-3d-container') || document.body;
|
|
941
|
+
|
|
942
|
+
var overlay = document.createElement('div');
|
|
943
|
+
overlay.id = 'jukebox-overlay';
|
|
944
|
+
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;';
|
|
945
|
+
|
|
946
|
+
var panel = document.createElement('div');
|
|
947
|
+
panel.style.cssText = 'background:#1a1a2e;border:2px solid #ff4488;border-radius:16px;padding:20px;width:500px;max-width:90vw;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 0 30px rgba(255,68,136,0.3);';
|
|
948
|
+
|
|
949
|
+
// Header
|
|
950
|
+
var header = document.createElement('div');
|
|
951
|
+
header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;';
|
|
952
|
+
header.innerHTML = '<div style="color:#ff4488;font-size:16px;font-weight:bold;text-shadow:0 0 8px #ff4488">JUKEBOX</div>';
|
|
953
|
+
var closeBtn = document.createElement('button');
|
|
954
|
+
closeBtn.textContent = 'Close (Esc)';
|
|
955
|
+
closeBtn.style.cssText = 'background:#333;color:#fff;border:1px solid #555;border-radius:6px;padding:4px 12px;cursor:pointer;font-size:11px;';
|
|
956
|
+
closeBtn.onclick = dismissJukebox;
|
|
957
|
+
header.appendChild(closeBtn);
|
|
958
|
+
panel.appendChild(header);
|
|
959
|
+
|
|
960
|
+
// Jukebox playlists — @AtmosphereBeatMusic (channel ID: UC72yf4UQp6w3ix5CjASZbUQ)
|
|
961
|
+
var JUKEBOX_PLAYLISTS = [
|
|
962
|
+
{ id: 'PLbUEFO6dm3dYsGrZQNU_W-VY-usRTfhU8', name: 'Atmosphere Beat' },
|
|
963
|
+
{ id: 'PLbUEFO6dm3dZvQ_8ma_9YfuOWdxO4G_Rn', name: 'Chill Vibes' },
|
|
964
|
+
{ id: 'PLbUEFO6dm3dZs8sKlvztA2eec_C4gHbUm', name: 'Deep Focus' },
|
|
965
|
+
{ id: 'PLbUEFO6dm3dYmU1PvubrMXRi4mYsi29Uj', name: 'Night Mode' },
|
|
966
|
+
];
|
|
967
|
+
// Playlist selector buttons
|
|
968
|
+
var selectorDiv = document.createElement('div');
|
|
969
|
+
selectorDiv.style.cssText = 'display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap;';
|
|
970
|
+
|
|
971
|
+
// Now-playing display area
|
|
972
|
+
var playerArea = document.createElement('div');
|
|
973
|
+
playerArea.style.cssText = 'flex:1;border-radius:8px;overflow:hidden;background:#111;min-height:200px;display:flex;align-items:center;justify-content:center;';
|
|
974
|
+
playerArea.innerHTML =
|
|
975
|
+
'<div style="text-align:center;padding:20px;color:#ccc">' +
|
|
976
|
+
'<div style="font-size:48px;margin-bottom:8px">\uD83C\uDFB5</div>' +
|
|
977
|
+
'<div style="font-size:14px;color:#ff4488;font-weight:bold">Select a Playlist</div>' +
|
|
978
|
+
'<div style="font-size:11px;color:#666;margin-top:4px">Music opens in a mini player window</div>' +
|
|
979
|
+
'</div>';
|
|
980
|
+
|
|
981
|
+
function playPlaylist(plId, plName, btnEl) {
|
|
982
|
+
// Try embed first — if channel enables embedding, this works seamlessly
|
|
983
|
+
var embedUrl = 'https://www.youtube-nocookie.com/embed/videoseries?list=' + plId + '&autoplay=1&shuffle=1&loop=1&rel=0';
|
|
984
|
+
|
|
985
|
+
// Open popup player window (400x300 — compact music player)
|
|
986
|
+
var popW = 480, popH = 360;
|
|
987
|
+
var popX = window.screenX + window.outerWidth - popW - 30;
|
|
988
|
+
var popY = window.screenY + 80;
|
|
989
|
+
var features = 'width=' + popW + ',height=' + popH + ',left=' + popX + ',top=' + popY + ',resizable=yes,scrollbars=no,toolbar=no,menubar=no,location=no,status=no';
|
|
990
|
+
|
|
991
|
+
// Close previous popup if open
|
|
992
|
+
if (_jukeboxPopup && !_jukeboxPopup.closed) _jukeboxPopup.close();
|
|
993
|
+
_jukeboxPopup = window.open(
|
|
994
|
+
'https://www.youtube.com/playlist?list=' + plId,
|
|
995
|
+
'jukebox_player',
|
|
996
|
+
features
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
// Update player area to show now-playing
|
|
1000
|
+
playerArea.innerHTML =
|
|
1001
|
+
'<div style="text-align:center;padding:20px;color:#ccc">' +
|
|
1002
|
+
'<div style="font-size:48px;margin-bottom:8px;animation:pulse 2s ease infinite">\uD83C\uDFB6</div>' +
|
|
1003
|
+
'<div style="font-size:15px;color:#ff4488;font-weight:bold">Now Playing</div>' +
|
|
1004
|
+
'<div style="font-size:13px;color:#e6edf3;margin-top:4px">' + (plName || 'Playlist') + '</div>' +
|
|
1005
|
+
'<div style="font-size:10px;color:#666;margin-top:8px">Playing in mini player window</div>' +
|
|
1006
|
+
'<div style="margin-top:14px;display:flex;gap:8px;justify-content:center">' +
|
|
1007
|
+
'<button id="jb-focus" style="padding:6px 16px;background:#ff4488;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:11px;font-weight:bold">\u25B6 Show Player</button>' +
|
|
1008
|
+
'<button id="jb-stop" style="padding:6px 16px;background:#333;color:#ff4488;border:1px solid #ff4488;border-radius:6px;cursor:pointer;font-size:11px">\u25A0 Stop</button>' +
|
|
1009
|
+
'</div>' +
|
|
1010
|
+
'</div>';
|
|
1011
|
+
|
|
1012
|
+
// Wire up buttons
|
|
1013
|
+
var focusBtn = playerArea.querySelector('#jb-focus');
|
|
1014
|
+
var stopBtn = playerArea.querySelector('#jb-stop');
|
|
1015
|
+
if (focusBtn) focusBtn.onclick = function() {
|
|
1016
|
+
if (_jukeboxPopup && !_jukeboxPopup.closed) _jukeboxPopup.focus();
|
|
1017
|
+
else playPlaylist(plId, plName, btnEl); // reopen if closed
|
|
1018
|
+
};
|
|
1019
|
+
if (stopBtn) stopBtn.onclick = function() {
|
|
1020
|
+
if (_jukeboxPopup && !_jukeboxPopup.closed) _jukeboxPopup.close();
|
|
1021
|
+
_jukeboxPopup = null;
|
|
1022
|
+
playerArea.innerHTML =
|
|
1023
|
+
'<div style="text-align:center;padding:20px;color:#ccc">' +
|
|
1024
|
+
'<div style="font-size:48px;margin-bottom:8px">\uD83C\uDFB5</div>' +
|
|
1025
|
+
'<div style="font-size:14px;color:#ff4488;font-weight:bold">Music Stopped</div>' +
|
|
1026
|
+
'<div style="font-size:11px;color:#666;margin-top:4px">Select a playlist to play again</div>' +
|
|
1027
|
+
'</div>';
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
// Highlight active playlist button
|
|
1031
|
+
var allBtns = selectorDiv.querySelectorAll('button[data-pl-id]');
|
|
1032
|
+
for (var bi = 0; bi < allBtns.length; bi++) {
|
|
1033
|
+
allBtns[bi].style.background = '#222';
|
|
1034
|
+
allBtns[bi].style.borderColor = '#444';
|
|
1035
|
+
}
|
|
1036
|
+
if (btnEl) { btnEl.style.background = '#ff448833'; btnEl.style.borderColor = '#ff4488'; }
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
for (var pi = 0; pi < JUKEBOX_PLAYLISTS.length; pi++) {
|
|
1040
|
+
var pl = JUKEBOX_PLAYLISTS[pi];
|
|
1041
|
+
var plBtn = document.createElement('button');
|
|
1042
|
+
plBtn.textContent = '\u266B ' + pl.name;
|
|
1043
|
+
plBtn.dataset.plId = pl.id;
|
|
1044
|
+
plBtn.dataset.plName = pl.name;
|
|
1045
|
+
plBtn.style.cssText = 'flex:1;min-width:80px;padding:8px;background:#222;border:1px solid #444;border-radius:8px;color:#e6edf3;font-size:11px;cursor:pointer;font-weight:500;transition:all 0.15s;';
|
|
1046
|
+
plBtn.addEventListener('mouseenter', function() { this.style.background = '#ff448822'; });
|
|
1047
|
+
plBtn.addEventListener('mouseleave', function() {
|
|
1048
|
+
if (this.style.borderColor !== 'rgb(255, 68, 136)') this.style.background = '#222';
|
|
1049
|
+
});
|
|
1050
|
+
plBtn.addEventListener('click', function() { playPlaylist(this.dataset.plId, this.dataset.plName, this); });
|
|
1051
|
+
selectorDiv.appendChild(plBtn);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
panel.appendChild(selectorDiv);
|
|
1055
|
+
panel.appendChild(playerArea);
|
|
1056
|
+
|
|
1057
|
+
// Add pulse animation for now-playing icon
|
|
1058
|
+
var styleTag = document.createElement('style');
|
|
1059
|
+
styleTag.textContent = '@keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.15)}}';
|
|
1060
|
+
panel.appendChild(styleTag);
|
|
1061
|
+
|
|
1062
|
+
// Channel link + hint
|
|
1063
|
+
var hint = document.createElement('div');
|
|
1064
|
+
hint.style.cssText = 'color:#888;font-size:10px;text-align:center;margin-top:8px;';
|
|
1065
|
+
hint.innerHTML = 'Music by <a href="https://www.youtube.com/@AtmosphereBeatMusic" target="_blank" style="color:#ff4488;text-decoration:none">@AtmosphereBeatMusic</a> • Escape to close (music keeps playing)';
|
|
1066
|
+
panel.appendChild(hint);
|
|
1067
|
+
|
|
1068
|
+
overlay.appendChild(panel);
|
|
1069
|
+
overlay.addEventListener('click', function(e) { if (e.target === overlay) dismissJukebox(); });
|
|
1070
|
+
document.body.appendChild(overlay);
|
|
1071
|
+
_jukeboxOverlay = overlay;
|
|
1072
|
+
|
|
1073
|
+
// Update jukebox label
|
|
1074
|
+
if (S._jukebox) {
|
|
1075
|
+
S._jukebox.playing = true;
|
|
1076
|
+
S._jukebox.label.innerHTML = '<div style="color:#ffdd44;font-size:10px">NOW PLAYING</div><div style="font-size:7px;color:#ff4488">Walk away to close</div>';
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Escape key to close
|
|
1080
|
+
var escHandler = function(e) {
|
|
1081
|
+
if (e.key === 'Escape') { dismissJukebox(); document.removeEventListener('keydown', escHandler); }
|
|
1082
|
+
};
|
|
1083
|
+
document.addEventListener('keydown', escHandler);
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
function dismissJukebox() {
|
|
1087
|
+
if (_jukeboxOverlay) {
|
|
1088
|
+
_jukeboxOverlay.remove();
|
|
1089
|
+
_jukeboxOverlay = null;
|
|
1090
|
+
}
|
|
1091
|
+
if (S._jukebox) {
|
|
1092
|
+
S._jukebox.playing = false;
|
|
1093
|
+
S._jukebox.label.innerHTML = '<div style="color:#ffdd44;font-size:10px">JUKEBOX</div><div style="font-size:7px;color:#aaa">Press E to play</div>';
|
|
1094
|
+
}
|
|
1095
|
+
}
|