let-them-talk 3.6.1 → 3.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1461 @@
1
+ import * as THREE from 'three';
2
+ import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
3
+ import { S } from './state.js';
4
+
5
+ // ============================================================
6
+ // TECH CAMPUS — Premium 2-floor environment
7
+ // Inspired by Google/Apple HQ: marble, glass, wood, RGB gaming
8
+ // ============================================================
9
+
10
+ var CAMPUS_W = 50;
11
+ var CAMPUS_D = 35;
12
+ var WALL_H = 6;
13
+ var MEZZ_H = 3.2;
14
+ var MEZZ_DEPTH = 12; // how far mezzanine extends from back wall
15
+
16
+ // Campus desk positions (gaming desk layout)
17
+ var CAMPUS_DESKS = [
18
+ // Main coder zone (center, 3 rows of 4)
19
+ { x: -4.5, z: 2 }, { x: -1.5, z: 2 }, { x: 1.5, z: 2 }, { x: 4.5, z: 2 },
20
+ { x: -4.5, z: -1 }, { x: -1.5, z: -1 }, { x: 1.5, z: -1 }, { x: 4.5, z: -1 },
21
+ { x: -4.5, z: -4 }, { x: -1.5, z: -4 }, { x: 1.5, z: -4 }, { x: 4.5, z: -4 },
22
+ // Designer wing (left, 2 rows of 2)
23
+ { x: -14, z: 1 }, { x: -11, z: 1 },
24
+ { x: -14, z: -2 }, { x: -11, z: -2 },
25
+ // Manager's private office desk (last position — assigned to first "Manager" role agent)
26
+ // Office at (12,5), desk at relative (0,1.5)=world(12,6.5), chair at relative (0,2.4)=world(12,7.4)
27
+ // Agent sits at deskPos.z + 0.7, so set z to 6.7 → agent at 7.4 (chair pos)
28
+ { x: 12, z: 6.7 },
29
+ ];
30
+
31
+ export function getCampusDeskPositions() {
32
+ return CAMPUS_DESKS;
33
+ }
34
+
35
+ export function buildCampusEnvironment() {
36
+ S.deskMeshes = [];
37
+
38
+ // Materials palette
39
+ var marbleMat = new THREE.MeshStandardMaterial({ color: 0xf0ece4, roughness: 0.15, metalness: 0.05 });
40
+ var marbleDarkMat = new THREE.MeshStandardMaterial({ color: 0xd4cfc7, roughness: 0.2 });
41
+ var walnutMat = new THREE.MeshStandardMaterial({ color: 0x5c3a1e, roughness: 0.6 });
42
+ var walnutLightMat = new THREE.MeshStandardMaterial({ color: 0x8B5E3C, roughness: 0.55 });
43
+ var chromeMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.1, metalness: 0.8 });
44
+ var glassMat = new THREE.MeshStandardMaterial({ color: 0xaaccee, transparent: true, opacity: 0.25, roughness: 0.05, metalness: 0.1 });
45
+ var glassFrameMat = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.2, metalness: 0.6 });
46
+ var darkMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.4 });
47
+ var carpetMat = new THREE.MeshStandardMaterial({ color: 0x2a2d3a, roughness: 0.95 });
48
+ var neonBlueMat = new THREE.MeshStandardMaterial({ color: 0x58a6ff, emissive: 0x58a6ff, emissiveIntensity: 0.6, roughness: 0.2 });
49
+ var neonPurpleMat = new THREE.MeshStandardMaterial({ color: 0xa855f7, emissive: 0xa855f7, emissiveIntensity: 0.5, roughness: 0.2 });
50
+ var neonGreenMat = new THREE.MeshStandardMaterial({ color: 0x22c55e, emissive: 0x22c55e, emissiveIntensity: 0.5, roughness: 0.2 });
51
+ var concreteMat = new THREE.MeshStandardMaterial({ color: 0x3a3d45, roughness: 0.85 });
52
+ var leatherBlackMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.7 });
53
+ var leatherBrownMat = new THREE.MeshStandardMaterial({ color: 0x6b3e26, roughness: 0.65 });
54
+ var goldMat = new THREE.MeshStandardMaterial({ color: 0xd4af37, roughness: 0.3, metalness: 0.7 });
55
+
56
+ // ========== FLOOR ==========
57
+ buildCampusFloor(marbleMat, marbleDarkMat, carpetMat);
58
+
59
+ // ========== WALLS & WINDOWS ==========
60
+ buildCampusWalls(concreteMat, glassMat, glassFrameMat);
61
+
62
+ // ========== CEILING & SKYLIGHTS ==========
63
+ buildCampusCeiling(concreteMat, glassMat);
64
+
65
+ // ========== MEZZANINE (2nd floor) ==========
66
+ buildMezzanine(concreteMat, chromeMat, glassMat, walnutMat);
67
+
68
+ // ========== STAIRCASE ==========
69
+ buildStaircase(marbleMat, chromeMat, glassMat);
70
+
71
+ // ========== GRAND LOBBY ==========
72
+ buildLobby(marbleMat, chromeMat, goldMat, walnutMat);
73
+
74
+ // ========== GAMING DESKS (main workspace, skip last = manager office) ==========
75
+ var managerDeskIdx = CAMPUS_DESKS.length - 1;
76
+ CAMPUS_DESKS.forEach(function(pos, i) {
77
+ if (i === managerDeskIdx) return; // manager office has its own built-in desk
78
+ buildGamingDesk(pos.x, pos.z, i);
79
+ });
80
+
81
+ // ========== MANAGER'S OFFICE (glass room, front right) ==========
82
+ buildManagerOffice(12, 5, glassMat, glassFrameMat, walnutMat, leatherBrownMat, chromeMat);
83
+
84
+ // ========== DESIGNER STUDIO (left wing) ==========
85
+ buildDesignerStudio(-12.5, 0, walnutLightMat, chromeMat);
86
+
87
+ // ========== BAR & CAFÉ (back left) ==========
88
+ buildBar(-14, -12, walnutMat, chromeMat, neonBlueMat, neonPurpleMat);
89
+
90
+ // ========== RECREATION CENTER (back center) ==========
91
+ buildRecCenter(0, -12, walnutMat, chromeMat, carpetMat);
92
+
93
+ // ========== GYM (back right) ==========
94
+ buildGym(14, -12, chromeMat, darkMat);
95
+
96
+ // ========== PLANTS & GREENERY ==========
97
+ buildCampusPlants();
98
+
99
+ // ========== PENDANT LIGHTS ==========
100
+ buildPendantLights();
101
+
102
+ // ========== GLASS PARTITIONS ==========
103
+ buildGlassPartitions(glassMat, glassFrameMat);
104
+
105
+ // ========== NEON SIGNS ==========
106
+ buildNeonSign('INNOVATE', -7, 4.5, -CAMPUS_D / 2 + 0.2, neonBlueMat);
107
+ buildNeonSign('CREATE', 7, 4.5, -CAMPUS_D / 2 + 0.2, neonPurpleMat);
108
+ buildNeonSign('BUILD', 0, MEZZ_H + 2, -CAMPUS_D / 2 + MEZZ_DEPTH + 0.2, neonGreenMat);
109
+ }
110
+
111
+ // ==================== FLOOR ====================
112
+ function buildCampusFloor(marbleMat, marbleDarkMat, carpetMat) {
113
+ // High-quality procedural dark marble tile floor
114
+ var size = 1024;
115
+ var cvs = document.createElement('canvas');
116
+ cvs.width = size; cvs.height = size;
117
+ var ctx = cvs.getContext('2d');
118
+ var tiles = 12;
119
+ var ts = size / tiles;
120
+
121
+ // Simple 2D noise function for marble veining
122
+ function noise(x, y) {
123
+ var n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
124
+ return n - Math.floor(n);
125
+ }
126
+ function smoothNoise(x, y) {
127
+ var ix = Math.floor(x), iy = Math.floor(y);
128
+ var fx = x - ix, fy = y - iy;
129
+ var a = noise(ix, iy), b = noise(ix + 1, iy);
130
+ var c = noise(ix, iy + 1), d = noise(ix + 1, iy + 1);
131
+ var u = fx * fx * (3 - 2 * fx), v = fy * fy * (3 - 2 * fy);
132
+ return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
133
+ }
134
+ function fbm(x, y) {
135
+ var val = 0, amp = 0.5;
136
+ for (var o = 0; o < 5; o++) {
137
+ val += smoothNoise(x, y) * amp;
138
+ x *= 2.1; y *= 2.1; amp *= 0.48;
139
+ }
140
+ return val;
141
+ }
142
+
143
+ for (var ti = 0; ti < tiles; ti++) {
144
+ for (var tj = 0; tj < tiles; tj++) {
145
+ var tx = ti * ts, ty = tj * ts;
146
+
147
+ // Alternating dark/darker marble tiles
148
+ var isDark = (ti + tj) % 2 === 0;
149
+ var baseR = isDark ? 28 : 22;
150
+ var baseG = isDark ? 30 : 24;
151
+ var baseB = isDark ? 38 : 30;
152
+
153
+ // Fill base tile color
154
+ ctx.fillStyle = 'rgb(' + baseR + ',' + baseG + ',' + baseB + ')';
155
+ ctx.fillRect(tx, ty, ts, ts);
156
+
157
+ // Marble veining (per-pixel noise)
158
+ var imgData = ctx.getImageData(tx, ty, ts, ts);
159
+ var data = imgData.data;
160
+ for (var py = 0; py < ts; py++) {
161
+ for (var px = 0; px < ts; px++) {
162
+ var wx = (ti * ts + px) / size * 6;
163
+ var wy = (tj * ts + py) / size * 6;
164
+
165
+ // Marble pattern: distorted sine wave + noise
166
+ var vein = Math.sin(wx * 3 + fbm(wx * 2, wy * 2) * 4) * 0.5 + 0.5;
167
+ var vein2 = Math.sin(wy * 2.5 + fbm(wx * 1.5 + 5, wy * 1.5 + 3) * 3.5) * 0.5 + 0.5;
168
+ var combined = vein * 0.6 + vein2 * 0.4;
169
+
170
+ // Color the veins — gold/white streaks on dark base
171
+ var veinStrength = Math.pow(combined, 3) * 0.35;
172
+ var nv = smoothNoise(wx * 4, wy * 4) * 0.08;
173
+
174
+ var idx = (py * ts + px) * 4;
175
+ // Base dark marble + gold/gray veins
176
+ data[idx] = Math.min(255, baseR + veinStrength * 120 + nv * 40); // R
177
+ data[idx + 1] = Math.min(255, baseG + veinStrength * 100 + nv * 35); // G
178
+ data[idx + 2] = Math.min(255, baseB + veinStrength * 60 + nv * 30); // B
179
+ data[idx + 3] = 255;
180
+ }
181
+ }
182
+ ctx.putImageData(imgData, tx, ty);
183
+
184
+ // Tile grout line (very thin, slightly lighter)
185
+ ctx.strokeStyle = 'rgba(50,52,60,0.8)';
186
+ ctx.lineWidth = 1.5;
187
+ ctx.strokeRect(tx + 0.5, ty + 0.5, ts - 1, ts - 1);
188
+ }
189
+ }
190
+
191
+ var floorTex = new THREE.CanvasTexture(cvs);
192
+ floorTex.wrapS = floorTex.wrapT = THREE.RepeatWrapping;
193
+ floorTex.anisotropy = 4;
194
+ var floorGeo = new THREE.PlaneGeometry(CAMPUS_W, CAMPUS_D);
195
+ var floorMeshMat = new THREE.MeshStandardMaterial({
196
+ map: floorTex, roughness: 0.12, metalness: 0.08
197
+ });
198
+ var floor = new THREE.Mesh(floorGeo, floorMeshMat);
199
+ floor.rotation.x = -Math.PI / 2;
200
+ floor.receiveShadow = true;
201
+ S.furnitureGroup.add(floor);
202
+
203
+ // Carpet runner in workspace zone (dark charcoal)
204
+ var carpet = new THREE.Mesh(new THREE.PlaneGeometry(14, 10), carpetMat);
205
+ carpet.rotation.x = -Math.PI / 2;
206
+ carpet.position.set(0, 0.01, -1);
207
+ carpet.receiveShadow = true;
208
+ S.furnitureGroup.add(carpet);
209
+ }
210
+
211
+ // ==================== WALLS & WINDOWS ====================
212
+ function buildCampusWalls(concreteMat, glassMat, frameMat) {
213
+ // Back wall (concrete with large windows)
214
+ var wallMat = new THREE.MeshStandardMaterial({ color: 0x2a2d35, roughness: 0.8, side: THREE.DoubleSide });
215
+
216
+ var backWall = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_W, WALL_H), wallMat);
217
+ backWall.position.set(0, WALL_H / 2, -CAMPUS_D / 2);
218
+ backWall.receiveShadow = true;
219
+ S.furnitureGroup.add(backWall);
220
+
221
+ var leftWall = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_D, WALL_H), wallMat);
222
+ leftWall.position.set(-CAMPUS_W / 2, WALL_H / 2, 0);
223
+ leftWall.rotation.y = Math.PI / 2;
224
+ leftWall.receiveShadow = true;
225
+ S.furnitureGroup.add(leftWall);
226
+
227
+ var rightWall = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_D, WALL_H), wallMat);
228
+ rightWall.position.set(CAMPUS_W / 2, WALL_H / 2, 0);
229
+ rightWall.rotation.y = -Math.PI / 2;
230
+ rightWall.receiveShadow = true;
231
+ S.furnitureGroup.add(rightWall);
232
+
233
+ // Front wall with entrance gap
234
+ var frontLeftWall = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_W / 2 - 4, WALL_H), wallMat);
235
+ frontLeftWall.position.set(-CAMPUS_W / 4 - 2, WALL_H / 2, CAMPUS_D / 2);
236
+ frontLeftWall.rotation.y = Math.PI;
237
+ S.furnitureGroup.add(frontLeftWall);
238
+
239
+ var frontRightWall = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_W / 2 - 4, WALL_H), wallMat);
240
+ frontRightWall.position.set(CAMPUS_W / 4 + 2, WALL_H / 2, CAMPUS_D / 2);
241
+ frontRightWall.rotation.y = Math.PI;
242
+ S.furnitureGroup.add(frontRightWall);
243
+
244
+ // Floor-to-ceiling windows (left and right walls)
245
+ var windowMat = new THREE.MeshStandardMaterial({
246
+ color: 0x87CEEB, emissive: 0x87CEEB, emissiveIntensity: 0.15, roughness: 0.05, transparent: true, opacity: 0.6
247
+ });
248
+ // Left wall windows
249
+ [-10, -5, 0, 5, 10].forEach(function(wz) {
250
+ var win = new THREE.Mesh(new THREE.PlaneGeometry(3, 4.5), windowMat);
251
+ win.position.set(-CAMPUS_W / 2 + 0.05, 3, wz);
252
+ win.rotation.y = Math.PI / 2;
253
+ S.furnitureGroup.add(win);
254
+ // Chrome frame
255
+ var frame = new THREE.Mesh(new THREE.BoxGeometry(0.04, 4.6, 3.1), frameMat);
256
+ frame.position.set(-CAMPUS_W / 2 + 0.02, 3, wz);
257
+ S.furnitureGroup.add(frame);
258
+ });
259
+ // Right wall windows
260
+ [-10, -5, 0, 5, 10].forEach(function(wz) {
261
+ var win = new THREE.Mesh(new THREE.PlaneGeometry(3, 4.5), windowMat);
262
+ win.position.set(CAMPUS_W / 2 - 0.05, 3, wz);
263
+ win.rotation.y = -Math.PI / 2;
264
+ S.furnitureGroup.add(win);
265
+ });
266
+ // Back wall windows (above mezzanine level)
267
+ [-15, -8, 0, 8, 15].forEach(function(wx) {
268
+ var win = new THREE.Mesh(new THREE.PlaneGeometry(4, 2), windowMat);
269
+ win.position.set(wx, 4.5, -CAMPUS_D / 2 + 0.05);
270
+ S.furnitureGroup.add(win);
271
+ });
272
+ }
273
+
274
+ // ==================== CEILING & SKYLIGHTS ====================
275
+ function buildCampusCeiling(concreteMat, glassMat) {
276
+ // Group for everything that should hide when camera is above roof
277
+ S._roofGroup = new THREE.Group();
278
+
279
+ // Main ceiling
280
+ var ceilingMat = new THREE.MeshStandardMaterial({ color: 0x1e2028, roughness: 0.9, side: THREE.DoubleSide });
281
+ var ceiling = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_W, CAMPUS_D), ceilingMat);
282
+ ceiling.rotation.x = Math.PI / 2;
283
+ ceiling.position.y = WALL_H;
284
+ S._roofGroup.add(ceiling);
285
+
286
+ // Skylights (glass rectangles in ceiling)
287
+ var skylightMat = new THREE.MeshStandardMaterial({
288
+ color: 0xaaddff, emissive: 0xaaddff, emissiveIntensity: 0.3, transparent: true, opacity: 0.4, side: THREE.DoubleSide
289
+ });
290
+ [[-6, 4], [6, 4], [-6, -4], [6, -4], [0, 0]].forEach(function(pos) {
291
+ var skylight = new THREE.Mesh(new THREE.PlaneGeometry(5, 3), skylightMat);
292
+ skylight.rotation.x = Math.PI / 2;
293
+ skylight.position.set(pos[0], WALL_H - 0.05, pos[1]);
294
+ S._roofGroup.add(skylight);
295
+ });
296
+
297
+ S.furnitureGroup.add(S._roofGroup);
298
+ }
299
+
300
+ // ==================== MEZZANINE ====================
301
+ function buildMezzanine(concreteMat, chromeMat, glassMat, walnutMat) {
302
+ // Platform
303
+ var mezzFloor = new THREE.Mesh(new THREE.BoxGeometry(CAMPUS_W - 2, 0.2, MEZZ_DEPTH), concreteMat);
304
+ mezzFloor.position.set(0, MEZZ_H, -CAMPUS_D / 2 + MEZZ_DEPTH / 2);
305
+ mezzFloor.castShadow = true; mezzFloor.receiveShadow = true;
306
+ S.furnitureGroup.add(mezzFloor);
307
+
308
+ // Floor surface (walnut)
309
+ var mezzTop = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_W - 2, MEZZ_DEPTH), walnutMat);
310
+ mezzTop.rotation.x = -Math.PI / 2;
311
+ mezzTop.position.set(0, MEZZ_H + 0.11, -CAMPUS_D / 2 + MEZZ_DEPTH / 2);
312
+ mezzTop.receiveShadow = true;
313
+ S.furnitureGroup.add(mezzTop);
314
+
315
+ // Glass railing along front edge
316
+ var railGlass = new THREE.Mesh(new THREE.PlaneGeometry(CAMPUS_W - 6, 1.1), glassMat);
317
+ railGlass.position.set(0, MEZZ_H + 0.65, -CAMPUS_D / 2 + MEZZ_DEPTH);
318
+ S.furnitureGroup.add(railGlass);
319
+ // Chrome rail bar on top
320
+ var railBar = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, CAMPUS_W - 6, 8), chromeMat);
321
+ railBar.rotation.z = Math.PI / 2;
322
+ railBar.position.set(0, MEZZ_H + 1.2, -CAMPUS_D / 2 + MEZZ_DEPTH);
323
+ S.furnitureGroup.add(railBar);
324
+
325
+ // Support columns
326
+ [-18, -9, 0, 9, 18].forEach(function(cx) {
327
+ var col = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, MEZZ_H, 12), chromeMat);
328
+ col.position.set(cx, MEZZ_H / 2, -CAMPUS_D / 2 + MEZZ_DEPTH);
329
+ col.castShadow = true;
330
+ S.furnitureGroup.add(col);
331
+ });
332
+
333
+ // Meeting pods on mezzanine (2 round tables with chairs)
334
+ [-10, 10].forEach(function(mx) {
335
+ // Round table
336
+ var table = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 0.06, 24), walnutMat);
337
+ table.position.set(mx, MEZZ_H + 0.85, -CAMPUS_D / 2 + 5);
338
+ table.castShadow = true;
339
+ S.furnitureGroup.add(table);
340
+ var tableLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.15, 0.7, 8), chromeMat);
341
+ tableLeg.position.set(mx, MEZZ_H + 0.46, -CAMPUS_D / 2 + 5);
342
+ S.furnitureGroup.add(tableLeg);
343
+ // 4 chairs around
344
+ for (var ci = 0; ci < 4; ci++) {
345
+ var ca = (ci / 4) * Math.PI * 2;
346
+ var cx2 = mx + Math.cos(ca) * 1.5;
347
+ var cz2 = -CAMPUS_D / 2 + 5 + Math.sin(ca) * 1.5;
348
+ buildModernChair(cx2, MEZZ_H + 0.11, cz2, ca + Math.PI, chromeMat);
349
+ }
350
+ });
351
+
352
+ // Lounge sofa on mezzanine
353
+ buildSofa(0, MEZZ_H + 0.11, -CAMPUS_D / 2 + 3);
354
+
355
+ // "UPPER DECK" sign
356
+ var signDiv = document.createElement('div');
357
+ signDiv.textContent = 'UPPER DECK';
358
+ signDiv.style.cssText = 'color:#d4af37;font-size:9px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:2px;';
359
+ var sign = new CSS2DObject(signDiv);
360
+ sign.position.set(0, MEZZ_H + 2, -CAMPUS_D / 2 + MEZZ_DEPTH);
361
+ S.furnitureGroup.add(sign);
362
+ }
363
+
364
+ // ==================== STAIRCASE ====================
365
+ function buildStaircase(marbleMat, chromeMat, glassMat) {
366
+ var stairX = 20;
367
+ var stairZ = -CAMPUS_D / 2 + MEZZ_DEPTH + 2;
368
+ var steps = 12;
369
+ var stepW = 2.5;
370
+ var stepH = MEZZ_H / steps;
371
+ var stepD = 0.5;
372
+
373
+ for (var i = 0; i < steps; i++) {
374
+ var step = new THREE.Mesh(new THREE.BoxGeometry(stepW, stepH, stepD), marbleMat);
375
+ step.position.set(stairX, stepH / 2 + i * stepH, stairZ - i * stepD);
376
+ step.castShadow = true; step.receiveShadow = true;
377
+ S.furnitureGroup.add(step);
378
+ }
379
+
380
+ // Glass side panels
381
+ var panelH = MEZZ_H + 1;
382
+ var panelD = steps * stepD;
383
+ var sidePanel = new THREE.Mesh(new THREE.PlaneGeometry(panelD, panelH), glassMat);
384
+ sidePanel.position.set(stairX + stepW / 2 + 0.05, panelH / 2, stairZ - panelD / 2);
385
+ sidePanel.rotation.y = Math.PI / 2;
386
+ S.furnitureGroup.add(sidePanel);
387
+
388
+ // Chrome handrail
389
+ var railLen = Math.sqrt(panelD * panelD + MEZZ_H * MEZZ_H);
390
+ var rail = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, railLen, 6), chromeMat);
391
+ rail.position.set(stairX + stepW / 2 + 0.08, MEZZ_H / 2 + 0.5, stairZ - panelD / 2);
392
+ rail.rotation.x = Math.atan2(MEZZ_H, panelD);
393
+ S.furnitureGroup.add(rail);
394
+ }
395
+
396
+ // ==================== GRAND LOBBY ====================
397
+ function buildLobby(marbleMat, chromeMat, goldMat, walnutMat) {
398
+ var lz = CAMPUS_D / 2 - 3;
399
+ var group = new THREE.Group();
400
+
401
+ // --- Modern reception desk (sleek angular shape) ---
402
+ var deskBodyMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.3, metalness: 0.15 });
403
+ // Front panel (angled, facing visitors)
404
+ var frontPanel = new THREE.Mesh(new THREE.BoxGeometry(4, 1.15, 0.12), deskBodyMat);
405
+ frontPanel.position.set(0, 0.58, lz + 0.5);
406
+ frontPanel.castShadow = true;
407
+ group.add(frontPanel);
408
+ // Side panels
409
+ [-2, 2].forEach(function(sx) {
410
+ var side = new THREE.Mesh(new THREE.BoxGeometry(0.12, 1.15, 1.2), deskBodyMat);
411
+ side.position.set(sx, 0.58, lz - 0.05);
412
+ side.castShadow = true;
413
+ group.add(side);
414
+ });
415
+ // Marble countertop
416
+ var counterTop = new THREE.Mesh(new THREE.BoxGeometry(4.2, 0.06, 1.4), marbleMat);
417
+ counterTop.position.set(0, 1.17, lz - 0.05);
418
+ counterTop.castShadow = true;
419
+ group.add(counterTop);
420
+ // Gold accent strip on front
421
+ var accentStrip = new THREE.Mesh(new THREE.BoxGeometry(3.9, 0.04, 0.005), goldMat);
422
+ accentStrip.position.set(0, 1.0, lz + 0.57);
423
+ group.add(accentStrip);
424
+ // LED underglow (blue)
425
+ var ledMat = new THREE.MeshStandardMaterial({ color: 0x58a6ff, emissive: 0x58a6ff, emissiveIntensity: 0.6, roughness: 0.2 });
426
+ var ledStrip = new THREE.Mesh(new THREE.BoxGeometry(3.8, 0.02, 0.02), ledMat);
427
+ ledStrip.position.set(0, 0.03, lz + 0.55);
428
+ group.add(ledStrip);
429
+
430
+ // Reception monitor (thin, on desk)
431
+ var monMat = new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 });
432
+ var mon = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.35, 0.02), monMat);
433
+ mon.position.set(-0.8, 1.45, lz - 0.1);
434
+ group.add(mon);
435
+ var monScreen = new THREE.Mesh(new THREE.PlaneGeometry(0.45, 0.3),
436
+ new THREE.MeshStandardMaterial({ color: 0x1a2a4a, emissive: 0x58a6ff, emissiveIntensity: 0.25, roughness: 0.1 }));
437
+ monScreen.position.set(-0.8, 1.45, lz - 0.088);
438
+ group.add(monScreen);
439
+ var monStand = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.2, 6), chromeMat);
440
+ monStand.position.set(-0.8, 1.28, lz - 0.1);
441
+ group.add(monStand);
442
+
443
+ // Keyboard on desk
444
+ var kbMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.5 });
445
+ var kb = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.01, 0.1), kbMat);
446
+ kb.position.set(-0.8, 1.2, lz - 0.4);
447
+ group.add(kb);
448
+
449
+ // --- Company logo wall (behind reception) ---
450
+ var logoWallMat = new THREE.MeshStandardMaterial({ color: 0x15181f, roughness: 0.7 });
451
+ var logoWall = new THREE.Mesh(new THREE.BoxGeometry(6, 3, 0.15), logoWallMat);
452
+ logoWall.position.set(0, 2, lz + 1.5);
453
+ logoWall.castShadow = true;
454
+ group.add(logoWall);
455
+ // Backlit logo text
456
+ var logoDiv = document.createElement('div');
457
+ logoDiv.textContent = 'LET THEM TALK';
458
+ logoDiv.style.cssText = 'color:#ffffff;font-size:16px;font-weight:900;font-family:Inter,sans-serif;letter-spacing:6px;text-shadow:0 0 20px rgba(88,166,255,0.6),0 0 40px rgba(88,166,255,0.3);';
459
+ var logoLabel = new CSS2DObject(logoDiv);
460
+ logoLabel.position.set(0, 2.8, lz + 1.6);
461
+ group.add(logoLabel);
462
+ // Accent light behind logo wall
463
+ var logoLight = new THREE.RectAreaLight ? null : null; // not available without addon
464
+ var logoSpot = new THREE.PointLight(0x58a6ff, 0.5, 6);
465
+ logoSpot.position.set(0, 3.5, lz + 1);
466
+ group.add(logoSpot);
467
+
468
+ // --- Water feature (low rectangular pool) ---
469
+ var poolFrame = new THREE.Mesh(new THREE.BoxGeometry(3, 0.2, 1.5),
470
+ new THREE.MeshStandardMaterial({ color: 0x2a2d35, roughness: 0.4 }));
471
+ poolFrame.position.set(0, 0.1, lz - 4);
472
+ poolFrame.castShadow = true;
473
+ group.add(poolFrame);
474
+ var waterMat = new THREE.MeshStandardMaterial({ color: 0x2a6090, roughness: 0.05, metalness: 0.3, transparent: true, opacity: 0.7 });
475
+ var water = new THREE.Mesh(new THREE.PlaneGeometry(2.7, 1.2), waterMat);
476
+ water.rotation.x = -Math.PI / 2;
477
+ water.position.set(0, 0.22, lz - 4);
478
+ group.add(water);
479
+ // Decorative stones in water
480
+ var stoneMat = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.7 });
481
+ [[-0.8, -0.3], [0.5, 0.2], [-0.2, 0.1], [0.9, -0.2], [-0.5, -0.1]].forEach(function(sp) {
482
+ var stone = new THREE.Mesh(new THREE.SphereGeometry(0.06 + Math.random() * 0.04, 6, 5), stoneMat);
483
+ stone.position.set(sp[0], 0.2, lz - 4 + sp[1]);
484
+ stone.scale.y = 0.5;
485
+ group.add(stone);
486
+ });
487
+
488
+ // --- Waiting area (2 modern benches) ---
489
+ var benchMat = new THREE.MeshStandardMaterial({ color: 0x2a2a3a, roughness: 0.6 });
490
+ [-5, 5].forEach(function(bx) {
491
+ var benchSeat = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.08, 0.6), benchMat);
492
+ benchSeat.position.set(bx, 0.45, lz - 2);
493
+ benchSeat.castShadow = true;
494
+ group.add(benchSeat);
495
+ // Chrome legs
496
+ [-1, 1].forEach(function(lx) {
497
+ var benchLeg = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.42, 0.5), chromeMat);
498
+ benchLeg.position.set(bx + lx, 0.22, lz - 2);
499
+ group.add(benchLeg);
500
+ });
501
+ });
502
+
503
+ // --- Pendant lights above reception ---
504
+ [-1.2, 0, 1.2].forEach(function(px) {
505
+ var wire = new THREE.Mesh(new THREE.CylinderGeometry(0.005, 0.005, 2.5, 4),
506
+ new THREE.MeshStandardMaterial({ color: 0x333333 }));
507
+ wire.position.set(px, WALL_H - 1.25, lz);
508
+ group.add(wire);
509
+ var shade = new THREE.Mesh(new THREE.SphereGeometry(0.12, 12, 10),
510
+ new THREE.MeshStandardMaterial({ color: 0xffeedd, emissive: 0xffeedd, emissiveIntensity: 0.4, transparent: true, opacity: 0.8 }));
511
+ shade.position.set(px, WALL_H - 2.6, lz);
512
+ group.add(shade);
513
+ });
514
+ // Warm light for reception area
515
+ var receptionLight = new THREE.PointLight(0xffeedd, 0.4, 8);
516
+ receptionLight.position.set(0, 4, lz);
517
+ group.add(receptionLight);
518
+
519
+ // RECEPTION sign (gold, above logo wall)
520
+ var signDiv = document.createElement('div');
521
+ signDiv.textContent = 'RECEPTION';
522
+ signDiv.style.cssText = 'color:#d4af37;font-size:10px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:3px;';
523
+ var sign = new CSS2DObject(signDiv);
524
+ sign.position.set(0, 4.5, lz);
525
+ group.add(sign);
526
+
527
+ S.furnitureGroup.add(group);
528
+ }
529
+
530
+ // ==================== GAMING DESK ====================
531
+ function buildGamingDesk(x, z, index) {
532
+ var group = new THREE.Group();
533
+ group.position.set(x, 0, z);
534
+
535
+ // L-shaped desk (main + side wing)
536
+ var deskColor = 0x1a1a2e;
537
+ var deskMat = new THREE.MeshStandardMaterial({ color: deskColor, roughness: 0.3, metalness: 0.1 });
538
+
539
+ // Main desktop
540
+ var mainTop = new THREE.Mesh(new THREE.BoxGeometry(2, 0.05, 0.9), deskMat);
541
+ mainTop.position.y = 0.76; mainTop.castShadow = true; mainTop.receiveShadow = true;
542
+ group.add(mainTop);
543
+
544
+ // RGB LED strip under desk edge (front)
545
+ var rgbColors = [0x58a6ff, 0xa855f7, 0x22c55e, 0xef4444, 0x06b6d4, 0xec4899];
546
+ var rgbColor = rgbColors[index % rgbColors.length];
547
+ var rgbMat = new THREE.MeshStandardMaterial({ color: rgbColor, emissive: rgbColor, emissiveIntensity: 0.8, roughness: 0.2 });
548
+ var rgbStrip = new THREE.Mesh(new THREE.BoxGeometry(1.9, 0.015, 0.015), rgbMat);
549
+ rgbStrip.position.set(0, 0.74, 0.44);
550
+ group.add(rgbStrip);
551
+
552
+ // Carbon fiber legs (angular, gaming style)
553
+ var legMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.4, metalness: 0.2 });
554
+ [[-0.85, -0.35], [-0.85, 0.35], [0.85, -0.35], [0.85, 0.35]].forEach(function(p) {
555
+ var leg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.76, 0.06), legMat);
556
+ leg.position.set(p[0], 0.38, p[1]);
557
+ leg.castShadow = true;
558
+ group.add(leg);
559
+ });
560
+
561
+ // Curved ultrawide monitor (wider, thinner)
562
+ var monBody = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.35, 0.03), new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 }));
563
+ monBody.position.set(0, 1.15, -0.25);
564
+ monBody.castShadow = true;
565
+ group.add(monBody);
566
+
567
+ // Monitor screen
568
+ var screenGeo = new THREE.PlaneGeometry(0.64, 0.3);
569
+ var screenMat = new THREE.MeshStandardMaterial({
570
+ color: 0x333333, emissive: 0x333333, emissiveIntensity: 0.1, roughness: 0.2
571
+ });
572
+ var screen = new THREE.Mesh(screenGeo, screenMat);
573
+ screen.position.set(0, 1.15, -0.234);
574
+ group.add(screen);
575
+
576
+ // Monitor stand (V-shaped, chrome)
577
+ var standMat = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.15, metalness: 0.7 });
578
+ var standArm = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.25, 0.04), standMat);
579
+ standArm.position.set(0, 0.92, -0.25);
580
+ group.add(standArm);
581
+ var standBase = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.02, 0.15), standMat);
582
+ standBase.position.set(0, 0.78, -0.25);
583
+ group.add(standBase);
584
+
585
+ // PC tower under desk (with RGB glow)
586
+ var pcMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.3 });
587
+ var pcCase = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.45, 0.45), pcMat);
588
+ pcCase.position.set(0.7, 0.23, 0);
589
+ pcCase.castShadow = true;
590
+ group.add(pcCase);
591
+ // RGB glass panel on PC
592
+ var pcGlowMat = new THREE.MeshStandardMaterial({ color: rgbColor, emissive: rgbColor, emissiveIntensity: 0.4, transparent: true, opacity: 0.5 });
593
+ var pcGlow = new THREE.Mesh(new THREE.PlaneGeometry(0.18, 0.4), pcGlowMat);
594
+ pcGlow.position.set(0.7 + 0.115, 0.23, 0);
595
+ pcGlow.rotation.y = Math.PI / 2;
596
+ group.add(pcGlow);
597
+
598
+ // Keyboard
599
+ var kbMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.5 });
600
+ var kb = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.02, 0.12), kbMat);
601
+ kb.position.set(-0.1, 0.78, 0.15);
602
+ group.add(kb);
603
+
604
+ // Mouse + mousepad
605
+ var padMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.8 });
606
+ var pad = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.005, 0.2), padMat);
607
+ pad.position.set(0.3, 0.765, 0.15);
608
+ group.add(pad);
609
+ var mouseMat2 = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3 });
610
+ var mouse = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.02, 0.07), mouseMat2);
611
+ mouse.position.set(0.3, 0.78, 0.15);
612
+ group.add(mouse);
613
+
614
+ // Gaming chair (racing style)
615
+ buildGamingChair(group, 0, 0.7, rgbColor);
616
+
617
+ S.furnitureGroup.add(group);
618
+ S.deskMeshes.push({ group: group, screen: screen, screenMat: screenMat, index: index, x: x, z: z });
619
+ }
620
+
621
+ // ==================== GAMING CHAIR ====================
622
+ function buildGamingChair(parent, cx, cz, accentColor) {
623
+ var chairGroup = new THREE.Group();
624
+ chairGroup.position.set(cx, 0, cz);
625
+
626
+ var baseMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.4, metalness: 0.3 });
627
+ var seatMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.65 });
628
+ var accentMat = new THREE.MeshStandardMaterial({ color: accentColor, roughness: 0.5 });
629
+
630
+ // 5-star base
631
+ var baseHub = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.04, 12), baseMat);
632
+ baseHub.position.y = 0.05;
633
+ chairGroup.add(baseHub);
634
+ for (var i = 0; i < 5; i++) {
635
+ var a = (i / 5) * Math.PI * 2;
636
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.02, 0.03), baseMat);
637
+ arm.position.set(Math.cos(a) * 0.15, 0.04, Math.sin(a) * 0.15);
638
+ arm.rotation.y = -a;
639
+ chairGroup.add(arm);
640
+ // Wheel
641
+ var wheel = new THREE.Mesh(new THREE.SphereGeometry(0.025, 6, 4), baseMat);
642
+ wheel.position.set(Math.cos(a) * 0.28, 0.025, Math.sin(a) * 0.28);
643
+ chairGroup.add(wheel);
644
+ }
645
+
646
+ // Gas cylinder
647
+ var cyl = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.35, 8), baseMat);
648
+ cyl.position.y = 0.25;
649
+ chairGroup.add(cyl);
650
+
651
+ // Seat
652
+ var seat = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.08, 0.42), seatMat);
653
+ seat.position.y = 0.46;
654
+ seat.castShadow = true;
655
+ chairGroup.add(seat);
656
+
657
+ // Backrest (tall, racing-style with wings)
658
+ var back = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.55, 0.06), seatMat);
659
+ back.position.set(0, 0.78, 0.2);
660
+ back.castShadow = true;
661
+ chairGroup.add(back);
662
+
663
+ // Headrest
664
+ var headrest = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.1, 0.06), seatMat);
665
+ headrest.position.set(0, 1.1, 0.2);
666
+ chairGroup.add(headrest);
667
+
668
+ // Accent stripes on backrest
669
+ var stripe1 = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.5, 0.005), accentMat);
670
+ stripe1.position.set(-0.12, 0.78, 0.17);
671
+ chairGroup.add(stripe1);
672
+ var stripe2 = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.5, 0.005), accentMat);
673
+ stripe2.position.set(0.12, 0.78, 0.17);
674
+ chairGroup.add(stripe2);
675
+
676
+ // Armrests
677
+ [-0.22, 0.22].forEach(function(ax) {
678
+ var armPost = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.2, 0.03), baseMat);
679
+ armPost.position.set(ax, 0.55, 0.05);
680
+ chairGroup.add(armPost);
681
+ var armPad = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.02, 0.2), seatMat);
682
+ armPad.position.set(ax, 0.66, 0.05);
683
+ chairGroup.add(armPad);
684
+ });
685
+
686
+ parent.add(chairGroup);
687
+ }
688
+
689
+ // ==================== MODERN CHAIR (for meeting rooms) ====================
690
+ function buildModernChair(x, y, z, rotation, chromeMat) {
691
+ var group = new THREE.Group();
692
+ group.position.set(x, y, z);
693
+ group.rotation.y = rotation;
694
+
695
+ var seatMat = new THREE.MeshStandardMaterial({ color: 0x333340, roughness: 0.7 });
696
+ var seat = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.05, 0.4), seatMat);
697
+ seat.position.y = 0.45; seat.castShadow = true;
698
+ group.add(seat);
699
+ var back = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.4, 0.04), seatMat);
700
+ back.position.set(0, 0.7, 0.18); back.castShadow = true;
701
+ group.add(back);
702
+ var post = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.4, 6), chromeMat);
703
+ post.position.y = 0.22;
704
+ group.add(post);
705
+
706
+ S.furnitureGroup.add(group);
707
+ }
708
+
709
+ // ==================== SOFA ====================
710
+ function buildSofa(x, y, z) {
711
+ var group = new THREE.Group();
712
+ group.position.set(x, y, z);
713
+
714
+ var sofaMat = new THREE.MeshStandardMaterial({ color: 0x2a2a3e, roughness: 0.75 });
715
+ // Base
716
+ var base = new THREE.Mesh(new THREE.BoxGeometry(3, 0.35, 0.9), sofaMat);
717
+ base.position.y = 0.2; base.castShadow = true;
718
+ group.add(base);
719
+ // Backrest
720
+ var backrest = new THREE.Mesh(new THREE.BoxGeometry(3, 0.5, 0.2), sofaMat);
721
+ backrest.position.set(0, 0.55, -0.35); backrest.castShadow = true;
722
+ group.add(backrest);
723
+ // Armrests
724
+ [-1.4, 1.4].forEach(function(ax) {
725
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 0.9), sofaMat);
726
+ arm.position.set(ax, 0.4, 0); arm.castShadow = true;
727
+ group.add(arm);
728
+ });
729
+ // Cushions
730
+ var cushionMat = new THREE.MeshStandardMaterial({ color: 0x3a3a5e, roughness: 0.8 });
731
+ [-0.8, 0, 0.8].forEach(function(cx2) {
732
+ var cushion = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.1, 0.7), cushionMat);
733
+ cushion.position.set(cx2, 0.42, 0.05);
734
+ group.add(cushion);
735
+ });
736
+
737
+ S.furnitureGroup.add(group);
738
+ }
739
+
740
+ // ==================== MANAGER'S OFFICE ====================
741
+ function buildManagerOffice(x, z, glassMat, frameMat, walnutMat, leatherMat, chromeMat) {
742
+ var offW = 8, offD = 7, wallH = 4;
743
+ var group = new THREE.Group();
744
+ group.position.set(x, 0, z);
745
+
746
+ // --- Raised floor (dark walnut) ---
747
+ var floorMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1a, roughness: 0.5 });
748
+ var floor = new THREE.Mesh(new THREE.BoxGeometry(offW, 0.06, offD), floorMat);
749
+ floor.position.y = 0.03; floor.receiveShadow = true;
750
+ group.add(floor);
751
+
752
+ // --- Glass walls with frosted privacy strip ---
753
+ var clearGlass = new THREE.MeshStandardMaterial({ color: 0xaaccee, transparent: true, opacity: 0.2, roughness: 0.05, metalness: 0.1, side: THREE.DoubleSide });
754
+ var frostedGlass = new THREE.MeshStandardMaterial({ color: 0xd0d8e8, transparent: true, opacity: 0.5, roughness: 0.4, side: THREE.DoubleSide });
755
+
756
+ // Front wall (with door gap in center)
757
+ var doorW = 1.2;
758
+ // Left section of front wall
759
+ var fwLeft = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, wallH), clearGlass);
760
+ fwLeft.position.set(-(offW + doorW) / 4, wallH / 2, -offD / 2);
761
+ group.add(fwLeft);
762
+ // Right section of front wall
763
+ var fwRight = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, wallH), clearGlass);
764
+ fwRight.position.set((offW + doorW) / 4, wallH / 2, -offD / 2);
765
+ group.add(fwRight);
766
+ // Frosted strip on front walls (waist-height privacy)
767
+ var frostLeft = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, 0.8), frostedGlass);
768
+ frostLeft.position.set(-(offW + doorW) / 4, 1.2, -offD / 2 + 0.01);
769
+ group.add(frostLeft);
770
+ var frostRight = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, 0.8), frostedGlass);
771
+ frostRight.position.set((offW + doorW) / 4, 1.2, -offD / 2 + 0.01);
772
+ group.add(frostRight);
773
+
774
+ // Glass sliding door (animated)
775
+ var doorGlass = new THREE.MeshStandardMaterial({ color: 0xbbddff, transparent: true, opacity: 0.3, roughness: 0.05, side: THREE.DoubleSide });
776
+ var door = new THREE.Mesh(new THREE.PlaneGeometry(doorW, wallH - 0.2), doorGlass);
777
+ door.position.set(0, wallH / 2, -offD / 2);
778
+ group.add(door);
779
+ // Door handle (chrome bar)
780
+ var handleMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.1, metalness: 0.8 });
781
+ var handle = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.3, 0.04), handleMat);
782
+ handle.position.set(doorW / 2 - 0.1, 1.1, -offD / 2 + 0.03);
783
+ group.add(handle);
784
+ // Store door ref for animation
785
+ S._managerDoor = door;
786
+ S._managerDoorOpen = 0; // 0=closed, 1=open (lerp target)
787
+ S._managerDoorLerp = 0;
788
+ S._managerDoorClosedZ = -offD / 2;
789
+
790
+ // Left glass wall
791
+ var leftWall = new THREE.Mesh(new THREE.PlaneGeometry(offD, wallH), clearGlass);
792
+ leftWall.position.set(-offW / 2, wallH / 2, 0);
793
+ leftWall.rotation.y = Math.PI / 2;
794
+ group.add(leftWall);
795
+ var frostLeftW = new THREE.Mesh(new THREE.PlaneGeometry(offD, 0.8), frostedGlass);
796
+ frostLeftW.position.set(-offW / 2 + 0.01, 1.2, 0);
797
+ frostLeftW.rotation.y = Math.PI / 2;
798
+ group.add(frostLeftW);
799
+
800
+ // Right glass wall
801
+ var rightWall = new THREE.Mesh(new THREE.PlaneGeometry(offD, wallH), clearGlass);
802
+ rightWall.position.set(offW / 2, wallH / 2, 0);
803
+ rightWall.rotation.y = -Math.PI / 2;
804
+ group.add(rightWall);
805
+ var frostRightW = new THREE.Mesh(new THREE.PlaneGeometry(offD, 0.8), frostedGlass);
806
+ frostRightW.position.set(offW / 2 - 0.01, 1.2, 0);
807
+ frostRightW.rotation.y = -Math.PI / 2;
808
+ group.add(frostRightW);
809
+
810
+ // Back glass wall
811
+ var backWall = new THREE.Mesh(new THREE.PlaneGeometry(offW, wallH), clearGlass);
812
+ backWall.position.set(0, wallH / 2, offD / 2);
813
+ backWall.rotation.y = Math.PI;
814
+ group.add(backWall);
815
+ var frostBackW = new THREE.Mesh(new THREE.PlaneGeometry(offW, 0.8), frostedGlass);
816
+ frostBackW.position.set(0, 1.2, offD / 2 - 0.01);
817
+ frostBackW.rotation.y = Math.PI;
818
+ group.add(frostBackW);
819
+
820
+ // --- Chrome frame structure ---
821
+ // Top beams (all 4 sides)
822
+ // Front beam
823
+ var beamFront = new THREE.Mesh(new THREE.BoxGeometry(offW, 0.06, 0.06), frameMat);
824
+ beamFront.position.set(0, wallH, -offD / 2); group.add(beamFront);
825
+ // Back beam
826
+ var beamBack = new THREE.Mesh(new THREE.BoxGeometry(offW, 0.06, 0.06), frameMat);
827
+ beamBack.position.set(0, wallH, offD / 2); group.add(beamBack);
828
+ // Left beam
829
+ var beamLeft = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.06, offD), frameMat);
830
+ beamLeft.position.set(-offW / 2, wallH, 0); group.add(beamLeft);
831
+ // Right beam
832
+ var beamRight = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.06, offD), frameMat);
833
+ beamRight.position.set(offW / 2, wallH, 0); group.add(beamRight);
834
+ // Vertical corner posts (all 4 corners)
835
+ [[-offW / 2, -offD / 2], [-offW / 2, offD / 2], [offW / 2, -offD / 2], [offW / 2, offD / 2]].forEach(function(p) {
836
+ var post = new THREE.Mesh(new THREE.BoxGeometry(0.06, wallH, 0.06), frameMat);
837
+ post.position.set(p[0], wallH / 2, p[1]);
838
+ group.add(post);
839
+ });
840
+ // Door frame posts
841
+ [-doorW / 2 - 0.03, doorW / 2 + 0.03].forEach(function(dx) {
842
+ var doorPost = new THREE.Mesh(new THREE.BoxGeometry(0.06, wallH, 0.06), frameMat);
843
+ doorPost.position.set(dx, wallH / 2, -offD / 2);
844
+ group.add(doorPost);
845
+ });
846
+ // Door top beam
847
+ var doorTopBeam = new THREE.Mesh(new THREE.BoxGeometry(doorW + 0.12, 0.06, 0.06), frameMat);
848
+ doorTopBeam.position.set(0, wallH, -offD / 2);
849
+ group.add(doorTopBeam);
850
+
851
+ // --- L-shaped executive desk (walnut + marble top) ---
852
+ var marbleTopMat = new THREE.MeshStandardMaterial({ color: 0xf0ece4, roughness: 0.12, metalness: 0.05 });
853
+ // Main section
854
+ var deskMain = new THREE.Mesh(new THREE.BoxGeometry(2.8, 0.06, 1.2), walnutMat);
855
+ deskMain.position.set(0, 0.78, 1.5); deskMain.castShadow = true;
856
+ group.add(deskMain);
857
+ var marbleTop1 = new THREE.Mesh(new THREE.BoxGeometry(2.82, 0.015, 1.22), marbleTopMat);
858
+ marbleTop1.position.set(0, 0.82, 1.5);
859
+ group.add(marbleTop1);
860
+ // Side wing
861
+ var deskWing = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.06, 0.8), walnutMat);
862
+ deskWing.position.set(1.6, 0.78, 0.7); deskWing.castShadow = true;
863
+ group.add(deskWing);
864
+ var marbleTop2 = new THREE.Mesh(new THREE.BoxGeometry(1.22, 0.015, 0.82), marbleTopMat);
865
+ marbleTop2.position.set(1.6, 0.82, 0.7);
866
+ group.add(marbleTop2);
867
+ // Desk legs (chrome, elegant)
868
+ [[-1.2, 1], [-1.2, 2], [1.2, 2], [1.2, 1], [2.1, 0.4], [2.1, 1]].forEach(function(p) {
869
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.78, 8), chromeMat);
870
+ leg.position.set(p[0], 0.39, p[1]);
871
+ group.add(leg);
872
+ });
873
+ // Cable management panel (dark, under desk back)
874
+ var cablePanelMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.5 });
875
+ var cablePanel = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.5, 0.04), cablePanelMat);
876
+ cablePanel.position.set(0, 0.5, 0.9);
877
+ group.add(cablePanel);
878
+
879
+ // --- Dual ultrawide monitors ---
880
+ var monMat = new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 });
881
+ [-0.45, 0.45].forEach(function(mx) {
882
+ var mon = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.35, 0.025), monMat);
883
+ mon.position.set(mx, 1.15, 1.05); mon.castShadow = true;
884
+ group.add(mon);
885
+ // Screen
886
+ var scrMat = new THREE.MeshStandardMaterial({ color: 0x1a2a4a, emissive: 0x58a6ff, emissiveIntensity: 0.3, roughness: 0.1 });
887
+ var scr = new THREE.Mesh(new THREE.PlaneGeometry(0.55, 0.3), scrMat);
888
+ scr.position.set(mx, 1.15, 1.037);
889
+ group.add(scr);
890
+ // Stand
891
+ var stand = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.22, 6), chromeMat);
892
+ stand.position.set(mx, 0.95, 1.05);
893
+ group.add(stand);
894
+ });
895
+ // Monitor arm (chrome, connecting both)
896
+ var monArm = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.03, 0.03), chromeMat);
897
+ monArm.position.set(0, 1.0, 1.05);
898
+ group.add(monArm);
899
+
900
+ // --- Keyboard + mouse ---
901
+ var kbMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.5 });
902
+ var kb = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.015, 0.12), kbMat);
903
+ kb.position.set(-0.15, 0.835, 1.8);
904
+ group.add(kb);
905
+ var mouse = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.015, 0.06), kbMat);
906
+ mouse.position.set(0.35, 0.835, 1.8);
907
+ group.add(mouse);
908
+
909
+ // --- Premium leather executive chair ---
910
+ var chairG = new THREE.Group();
911
+ chairG.position.set(0, 0, 2.4);
912
+ // 5-star base
913
+ var baseMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3, metalness: 0.4 });
914
+ for (var ci = 0; ci < 5; ci++) {
915
+ var ca = (ci / 5) * Math.PI * 2;
916
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.02, 0.035), baseMat);
917
+ arm.position.set(Math.cos(ca) * 0.16, 0.04, Math.sin(ca) * 0.16);
918
+ arm.rotation.y = -ca;
919
+ chairG.add(arm);
920
+ }
921
+ var cylM = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 0.4, 8), chromeMat);
922
+ cylM.position.y = 0.26; chairG.add(cylM);
923
+ // Wide seat
924
+ var seatM = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.1, 0.5), leatherMat);
925
+ seatM.position.y = 0.5; seatM.castShadow = true; chairG.add(seatM);
926
+ // Tall padded backrest
927
+ var backM = new THREE.Mesh(new THREE.BoxGeometry(0.48, 0.7, 0.08), leatherMat);
928
+ backM.position.set(0, 0.9, 0.24); backM.castShadow = true; chairG.add(backM);
929
+ // Headrest
930
+ var headM = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.12, 0.08), leatherMat);
931
+ headM.position.set(0, 1.3, 0.24); chairG.add(headM);
932
+ // Armrests
933
+ [-0.27, 0.27].forEach(function(ax) {
934
+ var armPost = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.25, 0.04), baseMat);
935
+ armPost.position.set(ax, 0.6, 0.08); chairG.add(armPost);
936
+ var armPad = new THREE.Mesh(new THREE.BoxGeometry(0.09, 0.03, 0.25), leatherMat);
937
+ armPad.position.set(ax, 0.73, 0.08); chairG.add(armPad);
938
+ });
939
+ group.add(chairG);
940
+
941
+ // --- Bookshelf (right wall, walnut) ---
942
+ var shelfGroup = new THREE.Group();
943
+ shelfGroup.position.set(3.2, 0, 0.5);
944
+ var shelfBack = new THREE.Mesh(new THREE.BoxGeometry(0.06, 2.2, 1.4), walnutMat);
945
+ shelfBack.position.y = 1.1; shelfBack.castShadow = true;
946
+ shelfGroup.add(shelfBack);
947
+ [0.05, 0.55, 1.1, 1.65, 2.15].forEach(function(sy) {
948
+ var shelf = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.03, 1.4), walnutMat);
949
+ shelf.position.set(0.12, sy, 0); shelf.receiveShadow = true;
950
+ shelfGroup.add(shelf);
951
+ });
952
+ // Books
953
+ var bookColors = [0xc0392b, 0x2980b9, 0x8e44ad, 0xd4a24e, 0x1abc9c, 0x2c3e50];
954
+ [0.09, 0.59, 1.14, 1.69].forEach(function(sy, si) {
955
+ var startZ = -0.55;
956
+ for (var bi2 = 0; bi2 < 5; bi2++) {
957
+ var bh = 0.32 + Math.sin(si + bi2) * 0.08;
958
+ var bw = 0.04 + Math.sin(si * 3 + bi2) * 0.015;
959
+ var bMat = new THREE.MeshStandardMaterial({ color: bookColors[(si * 3 + bi2) % bookColors.length], roughness: 0.8 });
960
+ var book = new THREE.Mesh(new THREE.BoxGeometry(0.18, bh, bw), bMat);
961
+ book.position.set(0.16, sy + bh / 2, startZ);
962
+ shelfGroup.add(book);
963
+ startZ += bw + 0.02;
964
+ }
965
+ });
966
+ group.add(shelfGroup);
967
+
968
+ // --- Small sofa + coffee table ---
969
+ var sofaMat = new THREE.MeshStandardMaterial({ color: 0x2a1a0a, roughness: 0.7 });
970
+ var sofaBase = new THREE.Mesh(new THREE.BoxGeometry(2, 0.3, 0.7), sofaMat);
971
+ sofaBase.position.set(-2.5, 0.18, -0.5); sofaBase.castShadow = true;
972
+ group.add(sofaBase);
973
+ var sofaBack = new THREE.Mesh(new THREE.BoxGeometry(2, 0.4, 0.15), sofaMat);
974
+ sofaBack.position.set(-2.5, 0.45, -0.85); sofaBack.castShadow = true;
975
+ group.add(sofaBack);
976
+ // Cushions
977
+ var cushionMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1a, roughness: 0.8 });
978
+ [-3.1, -2.5, -1.9].forEach(function(cx) {
979
+ var cushion = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.06, 0.55), cushionMat);
980
+ cushion.position.set(cx, 0.36, -0.5);
981
+ group.add(cushion);
982
+ });
983
+ // Coffee table (glass top, chrome legs)
984
+ var coffeeGlassMat = new THREE.MeshStandardMaterial({ color: 0xccddee, transparent: true, opacity: 0.35, roughness: 0.05 });
985
+ var coffeeTop = new THREE.Mesh(new THREE.BoxGeometry(1, 0.03, 0.5), coffeeGlassMat);
986
+ coffeeTop.position.set(-2.5, 0.45, 0.2);
987
+ group.add(coffeeTop);
988
+ [-0.4, 0.4].forEach(function(lx) {
989
+ [-0.18, 0.18].forEach(function(lz) {
990
+ var cLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.42, 6), chromeMat);
991
+ cLeg.position.set(-2.5 + lx, 0.22, 0.2 + lz);
992
+ group.add(cLeg);
993
+ });
994
+ });
995
+
996
+ // --- Luxury plant ---
997
+ var planterMat = new THREE.MeshStandardMaterial({ color: 0x2a2a3a, roughness: 0.5 });
998
+ var planter = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.2, 0.5, 12), planterMat);
999
+ planter.position.set(3, 0.25, -2.5); planter.castShadow = true;
1000
+ group.add(planter);
1001
+ var leafMat = new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.8 });
1002
+ for (var pi = 0; pi < 6; pi++) {
1003
+ var pa = (pi / 6) * Math.PI * 2;
1004
+ var leaf = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 6), leafMat);
1005
+ leaf.position.set(3 + Math.cos(pa) * 0.15, 0.6, -2.5 + Math.sin(pa) * 0.15);
1006
+ group.add(leaf);
1007
+ }
1008
+ var topL = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 6), leafMat);
1009
+ topL.position.set(3, 0.75, -2.5); group.add(topL);
1010
+
1011
+ // --- Gold accent artwork frame on back wall ---
1012
+ var goldMat = new THREE.MeshStandardMaterial({ color: 0xd4af37, roughness: 0.3, metalness: 0.7 });
1013
+ // Frame
1014
+ var artFrameTop = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.06, 0.06), goldMat);
1015
+ artFrameTop.position.set(0, 3.2, offD / 2 - 0.08); group.add(artFrameTop);
1016
+ var artFrameBot = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.06, 0.06), goldMat);
1017
+ artFrameBot.position.set(0, 2.0, offD / 2 - 0.08); group.add(artFrameBot);
1018
+ var artFrameL = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.26, 0.06), goldMat);
1019
+ artFrameL.position.set(-0.8, 2.6, offD / 2 - 0.08); group.add(artFrameL);
1020
+ var artFrameR = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.26, 0.06), goldMat);
1021
+ artFrameR.position.set(0.8, 2.6, offD / 2 - 0.08); group.add(artFrameR);
1022
+ // Canvas inside frame (dark elegant)
1023
+ var artMat = new THREE.MeshStandardMaterial({ color: 0x1a2a3a, roughness: 0.8 });
1024
+ var art = new THREE.Mesh(new THREE.PlaneGeometry(1.5, 1.1), artMat);
1025
+ art.position.set(0, 2.6, offD / 2 - 0.06);
1026
+ art.rotation.y = Math.PI;
1027
+ group.add(art);
1028
+
1029
+ // --- Warm ambient lighting ---
1030
+ var warmLight1 = new THREE.PointLight(0xffeedd, 0.4, 8);
1031
+ warmLight1.position.set(0, 3.5, 1.5);
1032
+ group.add(warmLight1);
1033
+ var warmLight2 = new THREE.PointLight(0xffeedd, 0.2, 5);
1034
+ warmLight2.position.set(-2, 2, 0);
1035
+ group.add(warmLight2);
1036
+
1037
+ // --- Pendant light (premium, gold accent) ---
1038
+ var pendWire = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.008, 2), new THREE.MeshStandardMaterial({ color: 0x333333 }));
1039
+ pendWire.position.set(0, wallH - 1, 1.5);
1040
+ group.add(pendWire);
1041
+ var pendShade = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.3, 0.2, 12, 1, true),
1042
+ new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.4, metalness: 0.3, side: THREE.DoubleSide }));
1043
+ pendShade.position.set(0, wallH - 2.1, 1.5);
1044
+ group.add(pendShade);
1045
+ var pendRim = new THREE.Mesh(new THREE.TorusGeometry(0.3, 0.01, 6, 24), goldMat);
1046
+ pendRim.position.set(0, wallH - 2.2, 1.5);
1047
+ pendRim.rotation.x = Math.PI / 2;
1048
+ group.add(pendRim);
1049
+
1050
+ // --- "MANAGER" gold sign above door ---
1051
+ var signDiv = document.createElement('div');
1052
+ signDiv.textContent = 'MANAGER';
1053
+ signDiv.style.cssText = 'color:#d4af37;font-size:10px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:3px;text-shadow:0 0 6px rgba(212,175,55,0.4);';
1054
+ var sign = new CSS2DObject(signDiv);
1055
+ sign.position.set(0, wallH + 0.3, -offD / 2);
1056
+ group.add(sign);
1057
+
1058
+ S.furnitureGroup.add(group);
1059
+ S._managerOfficeGroup = group;
1060
+ S._managerOfficePos = { x: x, z: z };
1061
+
1062
+ // Register manager desk in deskMeshes so monitor screen system works
1063
+ var mgrDeskIdx = CAMPUS_DESKS.length - 1;
1064
+ var mgrScreenMat = new THREE.MeshStandardMaterial({ color: 0x333333, emissive: 0x333333, emissiveIntensity: 0.1, roughness: 0.2 });
1065
+ S.deskMeshes[mgrDeskIdx] = { group: group, screen: null, screenMat: mgrScreenMat, index: mgrDeskIdx, x: x, z: z + 1.7 };
1066
+ }
1067
+
1068
+ // ==================== DESIGNER STUDIO ====================
1069
+ function buildDesignerStudio(x, z, walnutMat, chromeMat) {
1070
+ // Mood board wall
1071
+ var boardMat = new THREE.MeshStandardMaterial({ color: 0x3a3a4a, roughness: 0.5 });
1072
+ var board = new THREE.Mesh(new THREE.BoxGeometry(0.08, 2, 4), boardMat);
1073
+ board.position.set(x - 5.5, 1.5, z);
1074
+ board.castShadow = true;
1075
+ S.furnitureGroup.add(board);
1076
+ // Colorful sticky notes on board
1077
+ var noteColors = [0xfbbf24, 0xf87171, 0x34d399, 0x60a5fa, 0xa78bfa, 0xfb923c];
1078
+ for (var ni = 0; ni < 12; ni++) {
1079
+ var noteMat = new THREE.MeshStandardMaterial({ color: noteColors[ni % noteColors.length], roughness: 0.9 });
1080
+ var note = new THREE.Mesh(new THREE.PlaneGeometry(0.3, 0.3), noteMat);
1081
+ note.position.set(x - 5.44, 0.8 + Math.floor(ni / 4) * 0.5, z - 1.5 + (ni % 4) * 0.8);
1082
+ note.rotation.y = Math.PI / 2;
1083
+ S.furnitureGroup.add(note);
1084
+ }
1085
+
1086
+ // Standing desk
1087
+ var standDesk = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.06, 0.8), walnutMat);
1088
+ standDesk.position.set(x - 2, 1.1, z + 3);
1089
+ standDesk.castShadow = true;
1090
+ S.furnitureGroup.add(standDesk);
1091
+ // Adjustable legs
1092
+ [-0.7, 0.7].forEach(function(lx) {
1093
+ var standLeg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.1, 0.06), chromeMat);
1094
+ standLeg.position.set(x - 2 + lx, 0.55, z + 3);
1095
+ S.furnitureGroup.add(standLeg);
1096
+ });
1097
+
1098
+ // "DESIGN LAB" sign
1099
+ var signDiv = document.createElement('div');
1100
+ signDiv.textContent = 'DESIGN LAB';
1101
+ signDiv.style.cssText = 'color:#a855f7;font-size:9px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:2px;';
1102
+ var sign = new CSS2DObject(signDiv);
1103
+ sign.position.set(x, 3.5, z);
1104
+ S.furnitureGroup.add(sign);
1105
+ }
1106
+
1107
+ // ==================== BAR & CAFÉ ====================
1108
+ function buildBar(x, z, walnutMat, chromeMat, neonBlueMat, neonPurpleMat) {
1109
+ // Long bar counter
1110
+ var barTop = new THREE.Mesh(new THREE.BoxGeometry(6, 0.08, 1.2), walnutMat);
1111
+ barTop.position.set(x, 1.1, z); barTop.castShadow = true;
1112
+ S.furnitureGroup.add(barTop);
1113
+ var barFront = new THREE.Mesh(new THREE.BoxGeometry(6, 1.1, 0.1),
1114
+ new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.4 }));
1115
+ barFront.position.set(x, 0.55, z + 0.55);
1116
+ barFront.castShadow = true;
1117
+ S.furnitureGroup.add(barFront);
1118
+
1119
+ // LED strip under bar counter
1120
+ var barLed = new THREE.Mesh(new THREE.BoxGeometry(5.8, 0.02, 0.02), neonBlueMat);
1121
+ barLed.position.set(x, 1.02, z + 0.58);
1122
+ S.furnitureGroup.add(barLed);
1123
+
1124
+ // Bar stools (5)
1125
+ for (var si = 0; si < 5; si++) {
1126
+ var sx = x - 2 + si * 1;
1127
+ var stoolGroup = new THREE.Group();
1128
+ stoolGroup.position.set(sx, 0, z + 1.2);
1129
+ var stoolSeat = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.18, 0.06, 12),
1130
+ new THREE.MeshStandardMaterial({ color: 0x333340, roughness: 0.6 }));
1131
+ stoolSeat.position.y = 0.75;
1132
+ stoolGroup.add(stoolSeat);
1133
+ var stoolPost = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.7, 8), chromeMat);
1134
+ stoolPost.position.y = 0.38;
1135
+ stoolGroup.add(stoolPost);
1136
+ var stoolBase = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.2, 0.04, 12), chromeMat);
1137
+ stoolBase.position.y = 0.04;
1138
+ stoolGroup.add(stoolBase);
1139
+ S.furnitureGroup.add(stoolGroup);
1140
+ }
1141
+
1142
+ // Bottle shelf behind bar
1143
+ var shelfMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1a, roughness: 0.6 });
1144
+ [1.5, 2.2, 2.9].forEach(function(sy) {
1145
+ var shelf = new THREE.Mesh(new THREE.BoxGeometry(5.5, 0.04, 0.3), shelfMat);
1146
+ shelf.position.set(x, sy, z - 0.9);
1147
+ S.furnitureGroup.add(shelf);
1148
+ });
1149
+
1150
+ // Bottles on shelves
1151
+ var bottleColors = [0x2d8a4e, 0x8B4513, 0xd4af37, 0xcc3333, 0x1a5276, 0xf0f0f0];
1152
+ for (var bi = 0; bi < 15; bi++) {
1153
+ var bx = x - 2.5 + (bi % 5) * 1;
1154
+ var by = 1.55 + Math.floor(bi / 5) * 0.7;
1155
+ var bottleMat = new THREE.MeshStandardMaterial({ color: bottleColors[bi % bottleColors.length], roughness: 0.3, metalness: 0.1 });
1156
+ var bottle = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.25, 8), bottleMat);
1157
+ bottle.position.set(bx, by + 0.12, z - 0.85);
1158
+ S.furnitureGroup.add(bottle);
1159
+ var bottleNeck = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.03, 0.1, 6), bottleMat);
1160
+ bottleNeck.position.set(bx, by + 0.3, z - 0.85);
1161
+ S.furnitureGroup.add(bottleNeck);
1162
+ }
1163
+
1164
+ // Coffee machine
1165
+ var coffeeMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3, metalness: 0.2 });
1166
+ var coffee = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.5, 0.3), coffeeMat);
1167
+ coffee.position.set(x + 2.5, 1.4, z - 0.1);
1168
+ coffee.castShadow = true;
1169
+ S.furnitureGroup.add(coffee);
1170
+
1171
+ // "BAR" neon sign
1172
+ var signDiv = document.createElement('div');
1173
+ signDiv.textContent = 'BAR & CAFÉ';
1174
+ signDiv.style.cssText = 'color:#a855f7;font-size:10px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:2px;text-shadow:0 0 8px #a855f7;';
1175
+ var sign = new CSS2DObject(signDiv);
1176
+ sign.position.set(x, 3.5, z - 1);
1177
+ S.furnitureGroup.add(sign);
1178
+ }
1179
+
1180
+ // ==================== RECREATION CENTER ====================
1181
+ function buildRecCenter(x, z, walnutMat, chromeMat, carpetMat) {
1182
+ // Carpet area
1183
+ var recCarpet = new THREE.Mesh(new THREE.PlaneGeometry(10, 8), carpetMat);
1184
+ recCarpet.rotation.x = -Math.PI / 2;
1185
+ recCarpet.position.set(x, 0.01, z);
1186
+ recCarpet.receiveShadow = true;
1187
+ S.furnitureGroup.add(recCarpet);
1188
+
1189
+ // Pool table
1190
+ var ptGroup = new THREE.Group();
1191
+ ptGroup.position.set(x - 2, 0, z);
1192
+ var ptTop = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.1, 1.3),
1193
+ new THREE.MeshStandardMaterial({ color: 0x006633, roughness: 0.9 }));
1194
+ ptTop.position.y = 0.85; ptTop.castShadow = true;
1195
+ ptGroup.add(ptTop);
1196
+ var ptFrame = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.15, 1.4), walnutMat);
1197
+ ptFrame.position.y = 0.78; ptFrame.castShadow = true;
1198
+ ptGroup.add(ptFrame);
1199
+ // Legs
1200
+ [[-1.1, -0.55], [-1.1, 0.55], [1.1, -0.55], [1.1, 0.55]].forEach(function(p) {
1201
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.7, 8), walnutMat);
1202
+ leg.position.set(p[0], 0.35, p[1]);
1203
+ ptGroup.add(leg);
1204
+ });
1205
+ S.furnitureGroup.add(ptGroup);
1206
+
1207
+ // Foosball table
1208
+ var fbGroup = new THREE.Group();
1209
+ fbGroup.position.set(x + 2.5, 0, z);
1210
+ var fbBody = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.2, 0.75),
1211
+ new THREE.MeshStandardMaterial({ color: 0x2a1a0a, roughness: 0.6 }));
1212
+ fbBody.position.y = 0.85; fbBody.castShadow = true;
1213
+ fbGroup.add(fbBody);
1214
+ var fbField = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.02, 0.6),
1215
+ new THREE.MeshStandardMaterial({ color: 0x006633, roughness: 0.8 }));
1216
+ fbField.position.y = 0.96;
1217
+ fbGroup.add(fbField);
1218
+ // Legs
1219
+ [[-0.6, -0.3], [-0.6, 0.3], [0.6, -0.3], [0.6, 0.3]].forEach(function(p) {
1220
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.75, 6), chromeMat);
1221
+ leg.position.set(p[0], 0.38, p[1]);
1222
+ fbGroup.add(leg);
1223
+ });
1224
+ // Rods
1225
+ [-0.3, 0, 0.3].forEach(function(rz) {
1226
+ var rod = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.012, 0.9, 6), chromeMat);
1227
+ rod.position.set(0, 0.98, rz);
1228
+ rod.rotation.z = Math.PI / 2;
1229
+ fbGroup.add(rod);
1230
+ });
1231
+ S.furnitureGroup.add(fbGroup);
1232
+
1233
+ // Beanbags
1234
+ var bbColors = [0xe53e3e, 0x3b82f6, 0x22c55e, 0xa855f7];
1235
+ [{ x: -1, z: 3 }, { x: 1.5, z: 3.5 }, { x: 3, z: 2.5 }, { x: -2.5, z: 3.5 }].forEach(function(bp, bi) {
1236
+ var bbMat = new THREE.MeshStandardMaterial({ color: bbColors[bi], roughness: 0.9 });
1237
+ var bot = new THREE.Mesh(new THREE.SphereGeometry(0.45, 16, 12), bbMat);
1238
+ bot.position.set(x + bp.x, 0.22, z + bp.z);
1239
+ bot.scale.set(1, 0.5, 1);
1240
+ bot.castShadow = true;
1241
+ S.furnitureGroup.add(bot);
1242
+ });
1243
+
1244
+ // Big TV on the back
1245
+ var tvMat = new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 });
1246
+ var tv = new THREE.Mesh(new THREE.BoxGeometry(3, 1.8, 0.08), tvMat);
1247
+ tv.position.set(x, 2.5, z - 3.8);
1248
+ tv.castShadow = true;
1249
+ S.furnitureGroup.add(tv);
1250
+ var tvScreen = new THREE.Mesh(new THREE.PlaneGeometry(2.8, 1.6),
1251
+ new THREE.MeshStandardMaterial({ color: 0x1a2a4a, emissive: 0x1a2a4a, emissiveIntensity: 0.3, roughness: 0.1 }));
1252
+ tvScreen.position.set(x, 2.5, z - 3.75);
1253
+ S.furnitureGroup.add(tvScreen);
1254
+
1255
+ // "REC ZONE" sign
1256
+ var signDiv = document.createElement('div');
1257
+ signDiv.textContent = 'REC ZONE';
1258
+ signDiv.style.cssText = 'color:#22c55e;font-size:10px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:2px;text-shadow:0 0 8px #22c55e;';
1259
+ var sign = new CSS2DObject(signDiv);
1260
+ sign.position.set(x, 4.5, z);
1261
+ S.furnitureGroup.add(sign);
1262
+ }
1263
+
1264
+ // ==================== GYM ====================
1265
+ function buildGym(x, z, chromeMat, darkMat) {
1266
+ // Rubber floor
1267
+ var rubberMat = new THREE.MeshStandardMaterial({ color: 0x2a2a2a, roughness: 0.95 });
1268
+ var gymFloor = new THREE.Mesh(new THREE.PlaneGeometry(8, 8), rubberMat);
1269
+ gymFloor.rotation.x = -Math.PI / 2;
1270
+ gymFloor.position.set(x, 0.01, z);
1271
+ gymFloor.receiveShadow = true;
1272
+ S.furnitureGroup.add(gymFloor);
1273
+
1274
+ // Treadmill
1275
+ var tmGroup = new THREE.Group();
1276
+ tmGroup.position.set(x - 1.5, 0, z - 2);
1277
+ var tmBase = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.15, 1.6), darkMat);
1278
+ tmBase.position.y = 0.1; tmBase.castShadow = true;
1279
+ tmGroup.add(tmBase);
1280
+ var tmBelt = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.02, 1.3),
1281
+ new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.8 }));
1282
+ tmBelt.position.y = 0.19;
1283
+ tmGroup.add(tmBelt);
1284
+ // Handles
1285
+ [-0.3, 0.3].forEach(function(hx) {
1286
+ var handle = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 1, 6), chromeMat);
1287
+ handle.position.set(hx, 0.7, -0.6);
1288
+ tmGroup.add(handle);
1289
+ });
1290
+ var console2 = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.25, 0.08), darkMat);
1291
+ console2.position.set(0, 1.1, -0.65);
1292
+ tmGroup.add(console2);
1293
+ S.furnitureGroup.add(tmGroup);
1294
+
1295
+ // Dumbbell rack
1296
+ var rackBase = new THREE.Mesh(new THREE.BoxGeometry(2, 0.8, 0.4), chromeMat);
1297
+ rackBase.position.set(x + 1.5, 0.4, z - 3);
1298
+ rackBase.castShadow = true;
1299
+ S.furnitureGroup.add(rackBase);
1300
+ // Dumbbells on rack
1301
+ for (var di = 0; di < 5; di++) {
1302
+ var dbMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5, metalness: 0.4 });
1303
+ var db = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.3, 8), dbMat);
1304
+ db.position.set(x + 0.7 + di * 0.35, 0.9, z - 3);
1305
+ db.rotation.z = Math.PI / 2;
1306
+ S.furnitureGroup.add(db);
1307
+ }
1308
+
1309
+ // Yoga mat area
1310
+ var yogaMat = new THREE.MeshStandardMaterial({ color: 0x7c3aed, roughness: 0.9 });
1311
+ var mat = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.02, 1.8), yogaMat);
1312
+ mat.position.set(x + 2, 0.02, z + 1);
1313
+ S.furnitureGroup.add(mat);
1314
+ var mat2 = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.02, 1.8),
1315
+ new THREE.MeshStandardMaterial({ color: 0x06b6d4, roughness: 0.9 }));
1316
+ mat2.position.set(x + 3, 0.02, z + 1);
1317
+ S.furnitureGroup.add(mat2);
1318
+
1319
+ // "FITNESS" sign
1320
+ var signDiv = document.createElement('div');
1321
+ signDiv.textContent = 'FITNESS';
1322
+ signDiv.style.cssText = 'color:#ef4444;font-size:10px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:2px;text-shadow:0 0 8px #ef4444;';
1323
+ var sign = new CSS2DObject(signDiv);
1324
+ sign.position.set(x, 4, z);
1325
+ S.furnitureGroup.add(sign);
1326
+ }
1327
+
1328
+ // ==================== PLANTS ====================
1329
+ function buildCampusPlants() {
1330
+ var plantPositions = [
1331
+ [-20, 8], [20, 8], [-20, -5], [20, -5],
1332
+ [-8, 10], [8, 10], [-8, -8], [8, -8],
1333
+ [0, 10], [-15, 5], [15, 5],
1334
+ [-6, -10], [6, -10],
1335
+ ];
1336
+ plantPositions.forEach(function(pos) {
1337
+ buildLuxuryPlant(pos[0], pos[1]);
1338
+ });
1339
+
1340
+ // Indoor trees (taller, premium)
1341
+ [[-18, 0], [18, 0], [0, -7]].forEach(function(pos) {
1342
+ buildIndoorTree(pos[0], pos[1]);
1343
+ });
1344
+ }
1345
+
1346
+ function buildLuxuryPlant(x, z) {
1347
+ var group = new THREE.Group();
1348
+ group.position.set(x, 0, z);
1349
+ // Concrete planter
1350
+ var planterMat = new THREE.MeshStandardMaterial({ color: 0x4a4a5a, roughness: 0.6 });
1351
+ var planter = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.2, 0.5, 12), planterMat);
1352
+ planter.position.y = 0.25; planter.castShadow = true;
1353
+ group.add(planter);
1354
+ // Lush greenery
1355
+ var leafMat = new THREE.MeshStandardMaterial({ color: 0x2d8a4e, roughness: 0.8 });
1356
+ for (var i = 0; i < 6; i++) {
1357
+ var a = (i / 6) * Math.PI * 2;
1358
+ var leaf = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 6), leafMat);
1359
+ leaf.position.set(Math.cos(a) * 0.15, 0.6, Math.sin(a) * 0.15);
1360
+ leaf.castShadow = true;
1361
+ group.add(leaf);
1362
+ }
1363
+ var topLeaf = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 6), leafMat);
1364
+ topLeaf.position.y = 0.75;
1365
+ group.add(topLeaf);
1366
+ S.furnitureGroup.add(group);
1367
+ }
1368
+
1369
+ function buildIndoorTree(x, z) {
1370
+ var group = new THREE.Group();
1371
+ group.position.set(x, 0, z);
1372
+ // Large planter
1373
+ var planterMat = new THREE.MeshStandardMaterial({ color: 0x3a3a4a, roughness: 0.5 });
1374
+ var planter = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.35, 0.6, 12), planterMat);
1375
+ planter.position.y = 0.3; planter.castShadow = true;
1376
+ group.add(planter);
1377
+ // Trunk
1378
+ var trunkMat = new THREE.MeshStandardMaterial({ color: 0x5c3a1e, roughness: 0.8 });
1379
+ var trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 2.5, 8), trunkMat);
1380
+ trunk.position.y = 1.85; trunk.castShadow = true;
1381
+ group.add(trunk);
1382
+ // Canopy (layered spheres)
1383
+ var canopyMat = new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.85 });
1384
+ var canopyMat2 = new THREE.MeshStandardMaterial({ color: 0x2d8a4e, roughness: 0.85 });
1385
+ [{ y: 2.8, r: 0.6 }, { y: 3.2, r: 0.5 }, { y: 3.5, r: 0.35 }].forEach(function(c, ci) {
1386
+ var canopy = new THREE.Mesh(new THREE.SphereGeometry(c.r, 12, 10), ci % 2 === 0 ? canopyMat : canopyMat2);
1387
+ canopy.position.y = c.y;
1388
+ canopy.castShadow = true;
1389
+ group.add(canopy);
1390
+ });
1391
+ S.furnitureGroup.add(group);
1392
+ }
1393
+
1394
+ // ==================== PENDANT LIGHTS ====================
1395
+ function buildPendantLights() {
1396
+ var lightPositions = [
1397
+ [0, 2], [0, -1], [0, -4],
1398
+ [-4.5, 2], [-4.5, -1], [-4.5, -4],
1399
+ [4.5, 2], [4.5, -1], [4.5, -4],
1400
+ [-12, 1], [-12, -2],
1401
+ [12, 5],
1402
+ [-14, -12], [0, -12], [14, -12],
1403
+ ];
1404
+ lightPositions.forEach(function(pos) {
1405
+ buildPendantLight(pos[0], pos[1]);
1406
+ });
1407
+ }
1408
+
1409
+ function buildPendantLight(x, z) {
1410
+ var group = new THREE.Group();
1411
+ group.position.set(x, 0, z);
1412
+ // Wire
1413
+ var wireMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5 });
1414
+ var wire = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.008, 1.5, 4), wireMat);
1415
+ wire.position.y = WALL_H - 0.75;
1416
+ group.add(wire);
1417
+ // Shade (industrial style)
1418
+ var shadeMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.5, metalness: 0.3, side: THREE.DoubleSide });
1419
+ var shade = new THREE.Mesh(new THREE.ConeGeometry(0.25, 0.2, 12, 1, true), shadeMat);
1420
+ shade.position.y = WALL_H - 1.55;
1421
+ group.add(shade);
1422
+ // Warm light
1423
+ var light = new THREE.PointLight(0xffeedd, 0.25, 6);
1424
+ light.position.set(0, WALL_H - 1.7, 0);
1425
+ light.castShadow = false; // performance
1426
+ group.add(light);
1427
+ S.furnitureGroup.add(group);
1428
+ }
1429
+
1430
+ // ==================== GLASS PARTITIONS ====================
1431
+ function buildGlassPartitions(glassMat, frameMat) {
1432
+ // Between coder zone and rec area
1433
+ var partition1 = new THREE.Mesh(new THREE.PlaneGeometry(14, 2.5), glassMat);
1434
+ partition1.position.set(0, 1.25, -7);
1435
+ S.furnitureGroup.add(partition1);
1436
+ var frame1 = new THREE.Mesh(new THREE.BoxGeometry(14, 0.04, 0.04), frameMat);
1437
+ frame1.position.set(0, 2.5, -7);
1438
+ S.furnitureGroup.add(frame1);
1439
+
1440
+ // Between designer area and main
1441
+ var partition2 = new THREE.Mesh(new THREE.PlaneGeometry(10, 2.5), glassMat);
1442
+ partition2.position.set(-8, 1.25, 0);
1443
+ partition2.rotation.y = Math.PI / 2;
1444
+ S.furnitureGroup.add(partition2);
1445
+ }
1446
+
1447
+ // ==================== NEON SIGNS ====================
1448
+ function buildNeonSign(text, x, y, z, neonMat) {
1449
+ // Glow bar behind text
1450
+ var glowBar = new THREE.Mesh(new THREE.BoxGeometry(text.length * 0.4, 0.4, 0.04), neonMat);
1451
+ glowBar.position.set(x, y, z);
1452
+ S.furnitureGroup.add(glowBar);
1453
+ // CSS label
1454
+ var color = '#' + neonMat.color.getHexString();
1455
+ var div = document.createElement('div');
1456
+ div.textContent = text;
1457
+ div.style.cssText = 'color:' + color + ';font-size:12px;font-weight:900;font-family:Inter,sans-serif;letter-spacing:4px;text-shadow:0 0 12px ' + color + ',0 0 24px ' + color + ';';
1458
+ var label = new CSS2DObject(div);
1459
+ label.position.set(x, y, z + 0.05);
1460
+ S.furnitureGroup.add(label);
1461
+ }