let-them-talk 3.6.1 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1483 @@
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
+ // --- Feature wall with big TV monitor (behind reception) ---
450
+ var logoWallMat = new THREE.MeshStandardMaterial({ color: 0x15181f, roughness: 0.7 });
451
+ var logoWall = new THREE.Mesh(new THREE.BoxGeometry(6, 4, 0.15), logoWallMat);
452
+ logoWall.position.set(0, 2.5, lz + 1.5);
453
+ logoWall.castShadow = true;
454
+ group.add(logoWall);
455
+
456
+ // "LET THEM TALK" logo text above the TV
457
+ var logoDiv = document.createElement('div');
458
+ logoDiv.textContent = 'LET THEM TALK';
459
+ logoDiv.style.cssText = 'color:#ffffff;font-size:14px;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);';
460
+ var logoLabel = new CSS2DObject(logoDiv);
461
+ logoLabel.position.set(0, 4.3, lz + 1.6);
462
+ group.add(logoLabel);
463
+
464
+ // Big TV screen (dynamic canvas dashboard) — facing INTO the room (-z)
465
+ var tvFrame = new THREE.Mesh(new THREE.BoxGeometry(5, 2.8, 0.06),
466
+ new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 }));
467
+ tvFrame.position.set(0, 2.2, lz + 1.4);
468
+ tvFrame.castShadow = true;
469
+ group.add(tvFrame);
470
+ // Animated canvas
471
+ var tvW = 480, tvH = 300;
472
+ var tvCvs = document.createElement('canvas');
473
+ tvCvs.width = tvW; tvCvs.height = tvH;
474
+ var tvTex = new THREE.CanvasTexture(tvCvs);
475
+ tvTex.minFilter = THREE.LinearFilter;
476
+ var tvScreenMat = new THREE.MeshStandardMaterial({
477
+ map: tvTex, emissive: 0x58a6ff, emissiveIntensity: 0.2, roughness: 0.1
478
+ });
479
+ var tvScreen = new THREE.Mesh(new THREE.PlaneGeometry(4.6, 2.5), tvScreenMat);
480
+ tvScreen.position.set(0, 2.2, lz + 1.36);
481
+ tvScreen.rotation.y = Math.PI;
482
+ group.add(tvScreen);
483
+ S._tvScreen = { canvas: tvCvs, texture: tvTex, tickerOffset: 0 };
484
+
485
+ // Accent light on the wall
486
+ var logoSpot = new THREE.PointLight(0x58a6ff, 0.5, 6);
487
+ logoSpot.position.set(0, 4.2, lz + 1);
488
+ group.add(logoSpot);
489
+
490
+ // --- Water feature (low rectangular pool) ---
491
+ var poolFrame = new THREE.Mesh(new THREE.BoxGeometry(3, 0.2, 1.5),
492
+ new THREE.MeshStandardMaterial({ color: 0x2a2d35, roughness: 0.4 }));
493
+ poolFrame.position.set(0, 0.1, lz - 4);
494
+ poolFrame.castShadow = true;
495
+ group.add(poolFrame);
496
+ var waterMat = new THREE.MeshStandardMaterial({ color: 0x2a6090, roughness: 0.05, metalness: 0.3, transparent: true, opacity: 0.7 });
497
+ var water = new THREE.Mesh(new THREE.PlaneGeometry(2.7, 1.2), waterMat);
498
+ water.rotation.x = -Math.PI / 2;
499
+ water.position.set(0, 0.22, lz - 4);
500
+ group.add(water);
501
+ // Decorative stones in water
502
+ var stoneMat = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.7 });
503
+ [[-0.8, -0.3], [0.5, 0.2], [-0.2, 0.1], [0.9, -0.2], [-0.5, -0.1]].forEach(function(sp) {
504
+ var stone = new THREE.Mesh(new THREE.SphereGeometry(0.06 + Math.random() * 0.04, 6, 5), stoneMat);
505
+ stone.position.set(sp[0], 0.2, lz - 4 + sp[1]);
506
+ stone.scale.y = 0.5;
507
+ group.add(stone);
508
+ });
509
+
510
+ // --- Waiting area (2 modern benches) ---
511
+ var benchMat = new THREE.MeshStandardMaterial({ color: 0x2a2a3a, roughness: 0.6 });
512
+ [-5, 5].forEach(function(bx) {
513
+ var benchSeat = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.08, 0.6), benchMat);
514
+ benchSeat.position.set(bx, 0.45, lz - 2);
515
+ benchSeat.castShadow = true;
516
+ group.add(benchSeat);
517
+ // Chrome legs
518
+ [-1, 1].forEach(function(lx) {
519
+ var benchLeg = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.42, 0.5), chromeMat);
520
+ benchLeg.position.set(bx + lx, 0.22, lz - 2);
521
+ group.add(benchLeg);
522
+ });
523
+ });
524
+
525
+ // --- Pendant lights above reception ---
526
+ [-1.2, 0, 1.2].forEach(function(px) {
527
+ var wire = new THREE.Mesh(new THREE.CylinderGeometry(0.005, 0.005, 2.5, 4),
528
+ new THREE.MeshStandardMaterial({ color: 0x333333 }));
529
+ wire.position.set(px, WALL_H - 1.25, lz);
530
+ group.add(wire);
531
+ var shade = new THREE.Mesh(new THREE.SphereGeometry(0.12, 12, 10),
532
+ new THREE.MeshStandardMaterial({ color: 0xffeedd, emissive: 0xffeedd, emissiveIntensity: 0.4, transparent: true, opacity: 0.8 }));
533
+ shade.position.set(px, WALL_H - 2.6, lz);
534
+ group.add(shade);
535
+ });
536
+ // Warm light for reception area
537
+ var receptionLight = new THREE.PointLight(0xffeedd, 0.4, 8);
538
+ receptionLight.position.set(0, 4, lz);
539
+ group.add(receptionLight);
540
+
541
+ // RECEPTION sign (gold, above logo wall)
542
+ var signDiv = document.createElement('div');
543
+ signDiv.textContent = 'RECEPTION';
544
+ signDiv.style.cssText = 'color:#d4af37;font-size:10px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:3px;';
545
+ var sign = new CSS2DObject(signDiv);
546
+ sign.position.set(0, 4.5, lz);
547
+ group.add(sign);
548
+
549
+ S.furnitureGroup.add(group);
550
+ }
551
+
552
+ // ==================== GAMING DESK ====================
553
+ function buildGamingDesk(x, z, index) {
554
+ var group = new THREE.Group();
555
+ group.position.set(x, 0, z);
556
+
557
+ // L-shaped desk (main + side wing)
558
+ var deskColor = 0x1a1a2e;
559
+ var deskMat = new THREE.MeshStandardMaterial({ color: deskColor, roughness: 0.3, metalness: 0.1 });
560
+
561
+ // Main desktop
562
+ var mainTop = new THREE.Mesh(new THREE.BoxGeometry(2, 0.05, 0.9), deskMat);
563
+ mainTop.position.y = 0.76; mainTop.castShadow = true; mainTop.receiveShadow = true;
564
+ group.add(mainTop);
565
+
566
+ // RGB LED strip under desk edge (front)
567
+ var rgbColors = [0x58a6ff, 0xa855f7, 0x22c55e, 0xef4444, 0x06b6d4, 0xec4899];
568
+ var rgbColor = rgbColors[index % rgbColors.length];
569
+ var rgbMat = new THREE.MeshStandardMaterial({ color: rgbColor, emissive: rgbColor, emissiveIntensity: 0.8, roughness: 0.2 });
570
+ var rgbStrip = new THREE.Mesh(new THREE.BoxGeometry(1.9, 0.015, 0.015), rgbMat);
571
+ rgbStrip.position.set(0, 0.74, 0.44);
572
+ group.add(rgbStrip);
573
+
574
+ // Carbon fiber legs (angular, gaming style)
575
+ var legMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.4, metalness: 0.2 });
576
+ [[-0.85, -0.35], [-0.85, 0.35], [0.85, -0.35], [0.85, 0.35]].forEach(function(p) {
577
+ var leg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.76, 0.06), legMat);
578
+ leg.position.set(p[0], 0.38, p[1]);
579
+ leg.castShadow = true;
580
+ group.add(leg);
581
+ });
582
+
583
+ // Curved ultrawide monitor (wider, thinner)
584
+ var monBody = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.35, 0.03), new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 }));
585
+ monBody.position.set(0, 1.15, -0.25);
586
+ monBody.castShadow = true;
587
+ group.add(monBody);
588
+
589
+ // Monitor screen
590
+ var screenGeo = new THREE.PlaneGeometry(0.64, 0.3);
591
+ var screenMat = new THREE.MeshStandardMaterial({
592
+ color: 0x333333, emissive: 0x333333, emissiveIntensity: 0.1, roughness: 0.2
593
+ });
594
+ var screen = new THREE.Mesh(screenGeo, screenMat);
595
+ screen.position.set(0, 1.15, -0.234);
596
+ group.add(screen);
597
+
598
+ // Monitor stand (V-shaped, chrome)
599
+ var standMat = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.15, metalness: 0.7 });
600
+ var standArm = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.25, 0.04), standMat);
601
+ standArm.position.set(0, 0.92, -0.25);
602
+ group.add(standArm);
603
+ var standBase = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.02, 0.15), standMat);
604
+ standBase.position.set(0, 0.78, -0.25);
605
+ group.add(standBase);
606
+
607
+ // PC tower under desk (with RGB glow)
608
+ var pcMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.3 });
609
+ var pcCase = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.45, 0.45), pcMat);
610
+ pcCase.position.set(0.7, 0.23, 0);
611
+ pcCase.castShadow = true;
612
+ group.add(pcCase);
613
+ // RGB glass panel on PC
614
+ var pcGlowMat = new THREE.MeshStandardMaterial({ color: rgbColor, emissive: rgbColor, emissiveIntensity: 0.4, transparent: true, opacity: 0.5 });
615
+ var pcGlow = new THREE.Mesh(new THREE.PlaneGeometry(0.18, 0.4), pcGlowMat);
616
+ pcGlow.position.set(0.7 + 0.115, 0.23, 0);
617
+ pcGlow.rotation.y = Math.PI / 2;
618
+ group.add(pcGlow);
619
+
620
+ // Keyboard
621
+ var kbMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.5 });
622
+ var kb = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.02, 0.12), kbMat);
623
+ kb.position.set(-0.1, 0.78, 0.15);
624
+ group.add(kb);
625
+
626
+ // Mouse + mousepad
627
+ var padMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.8 });
628
+ var pad = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.005, 0.2), padMat);
629
+ pad.position.set(0.3, 0.765, 0.15);
630
+ group.add(pad);
631
+ var mouseMat2 = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3 });
632
+ var mouse = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.02, 0.07), mouseMat2);
633
+ mouse.position.set(0.3, 0.78, 0.15);
634
+ group.add(mouse);
635
+
636
+ // Gaming chair (racing style)
637
+ buildGamingChair(group, 0, 0.7, rgbColor);
638
+
639
+ S.furnitureGroup.add(group);
640
+ S.deskMeshes.push({ group: group, screen: screen, screenMat: screenMat, index: index, x: x, z: z });
641
+ }
642
+
643
+ // ==================== GAMING CHAIR ====================
644
+ function buildGamingChair(parent, cx, cz, accentColor) {
645
+ var chairGroup = new THREE.Group();
646
+ chairGroup.position.set(cx, 0, cz);
647
+
648
+ var baseMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.4, metalness: 0.3 });
649
+ var seatMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.65 });
650
+ var accentMat = new THREE.MeshStandardMaterial({ color: accentColor, roughness: 0.5 });
651
+
652
+ // 5-star base
653
+ var baseHub = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.04, 12), baseMat);
654
+ baseHub.position.y = 0.05;
655
+ chairGroup.add(baseHub);
656
+ for (var i = 0; i < 5; i++) {
657
+ var a = (i / 5) * Math.PI * 2;
658
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.02, 0.03), baseMat);
659
+ arm.position.set(Math.cos(a) * 0.15, 0.04, Math.sin(a) * 0.15);
660
+ arm.rotation.y = -a;
661
+ chairGroup.add(arm);
662
+ // Wheel
663
+ var wheel = new THREE.Mesh(new THREE.SphereGeometry(0.025, 6, 4), baseMat);
664
+ wheel.position.set(Math.cos(a) * 0.28, 0.025, Math.sin(a) * 0.28);
665
+ chairGroup.add(wheel);
666
+ }
667
+
668
+ // Gas cylinder
669
+ var cyl = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.35, 8), baseMat);
670
+ cyl.position.y = 0.25;
671
+ chairGroup.add(cyl);
672
+
673
+ // Seat
674
+ var seat = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.08, 0.42), seatMat);
675
+ seat.position.y = 0.46;
676
+ seat.castShadow = true;
677
+ chairGroup.add(seat);
678
+
679
+ // Backrest (tall, racing-style with wings)
680
+ var back = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.55, 0.06), seatMat);
681
+ back.position.set(0, 0.78, 0.2);
682
+ back.castShadow = true;
683
+ chairGroup.add(back);
684
+
685
+ // Headrest
686
+ var headrest = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.1, 0.06), seatMat);
687
+ headrest.position.set(0, 1.1, 0.2);
688
+ chairGroup.add(headrest);
689
+
690
+ // Accent stripes on backrest
691
+ var stripe1 = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.5, 0.005), accentMat);
692
+ stripe1.position.set(-0.12, 0.78, 0.17);
693
+ chairGroup.add(stripe1);
694
+ var stripe2 = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.5, 0.005), accentMat);
695
+ stripe2.position.set(0.12, 0.78, 0.17);
696
+ chairGroup.add(stripe2);
697
+
698
+ // Armrests
699
+ [-0.22, 0.22].forEach(function(ax) {
700
+ var armPost = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.2, 0.03), baseMat);
701
+ armPost.position.set(ax, 0.55, 0.05);
702
+ chairGroup.add(armPost);
703
+ var armPad = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.02, 0.2), seatMat);
704
+ armPad.position.set(ax, 0.66, 0.05);
705
+ chairGroup.add(armPad);
706
+ });
707
+
708
+ parent.add(chairGroup);
709
+ }
710
+
711
+ // ==================== MODERN CHAIR (for meeting rooms) ====================
712
+ function buildModernChair(x, y, z, rotation, chromeMat) {
713
+ var group = new THREE.Group();
714
+ group.position.set(x, y, z);
715
+ group.rotation.y = rotation;
716
+
717
+ var seatMat = new THREE.MeshStandardMaterial({ color: 0x333340, roughness: 0.7 });
718
+ var seat = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.05, 0.4), seatMat);
719
+ seat.position.y = 0.45; seat.castShadow = true;
720
+ group.add(seat);
721
+ var back = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.4, 0.04), seatMat);
722
+ back.position.set(0, 0.7, 0.18); back.castShadow = true;
723
+ group.add(back);
724
+ var post = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.4, 6), chromeMat);
725
+ post.position.y = 0.22;
726
+ group.add(post);
727
+
728
+ S.furnitureGroup.add(group);
729
+ }
730
+
731
+ // ==================== SOFA ====================
732
+ function buildSofa(x, y, z) {
733
+ var group = new THREE.Group();
734
+ group.position.set(x, y, z);
735
+
736
+ var sofaMat = new THREE.MeshStandardMaterial({ color: 0x2a2a3e, roughness: 0.75 });
737
+ // Base
738
+ var base = new THREE.Mesh(new THREE.BoxGeometry(3, 0.35, 0.9), sofaMat);
739
+ base.position.y = 0.2; base.castShadow = true;
740
+ group.add(base);
741
+ // Backrest
742
+ var backrest = new THREE.Mesh(new THREE.BoxGeometry(3, 0.5, 0.2), sofaMat);
743
+ backrest.position.set(0, 0.55, -0.35); backrest.castShadow = true;
744
+ group.add(backrest);
745
+ // Armrests
746
+ [-1.4, 1.4].forEach(function(ax) {
747
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 0.9), sofaMat);
748
+ arm.position.set(ax, 0.4, 0); arm.castShadow = true;
749
+ group.add(arm);
750
+ });
751
+ // Cushions
752
+ var cushionMat = new THREE.MeshStandardMaterial({ color: 0x3a3a5e, roughness: 0.8 });
753
+ [-0.8, 0, 0.8].forEach(function(cx2) {
754
+ var cushion = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.1, 0.7), cushionMat);
755
+ cushion.position.set(cx2, 0.42, 0.05);
756
+ group.add(cushion);
757
+ });
758
+
759
+ S.furnitureGroup.add(group);
760
+ }
761
+
762
+ // ==================== MANAGER'S OFFICE ====================
763
+ function buildManagerOffice(x, z, glassMat, frameMat, walnutMat, leatherMat, chromeMat) {
764
+ var offW = 8, offD = 7, wallH = 4;
765
+ var group = new THREE.Group();
766
+ group.position.set(x, 0, z);
767
+
768
+ // --- Raised floor (dark walnut) ---
769
+ var floorMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1a, roughness: 0.5 });
770
+ var floor = new THREE.Mesh(new THREE.BoxGeometry(offW, 0.06, offD), floorMat);
771
+ floor.position.y = 0.03; floor.receiveShadow = true;
772
+ group.add(floor);
773
+
774
+ // --- Glass walls with frosted privacy strip ---
775
+ var clearGlass = new THREE.MeshStandardMaterial({ color: 0xaaccee, transparent: true, opacity: 0.2, roughness: 0.05, metalness: 0.1, side: THREE.DoubleSide });
776
+ var frostedGlass = new THREE.MeshStandardMaterial({ color: 0xd0d8e8, transparent: true, opacity: 0.5, roughness: 0.4, side: THREE.DoubleSide });
777
+
778
+ // Front wall (with door gap in center)
779
+ var doorW = 1.2;
780
+ // Left section of front wall
781
+ var fwLeft = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, wallH), clearGlass);
782
+ fwLeft.position.set(-(offW + doorW) / 4, wallH / 2, -offD / 2);
783
+ group.add(fwLeft);
784
+ // Right section of front wall
785
+ var fwRight = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, wallH), clearGlass);
786
+ fwRight.position.set((offW + doorW) / 4, wallH / 2, -offD / 2);
787
+ group.add(fwRight);
788
+ // Frosted strip on front walls (waist-height privacy)
789
+ var frostLeft = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, 0.8), frostedGlass);
790
+ frostLeft.position.set(-(offW + doorW) / 4, 1.2, -offD / 2 + 0.01);
791
+ group.add(frostLeft);
792
+ var frostRight = new THREE.Mesh(new THREE.PlaneGeometry((offW - doorW) / 2, 0.8), frostedGlass);
793
+ frostRight.position.set((offW + doorW) / 4, 1.2, -offD / 2 + 0.01);
794
+ group.add(frostRight);
795
+
796
+ // Glass sliding door (animated)
797
+ var doorGlass = new THREE.MeshStandardMaterial({ color: 0xbbddff, transparent: true, opacity: 0.3, roughness: 0.05, side: THREE.DoubleSide });
798
+ var door = new THREE.Mesh(new THREE.PlaneGeometry(doorW, wallH - 0.2), doorGlass);
799
+ door.position.set(0, wallH / 2, -offD / 2);
800
+ group.add(door);
801
+ // Door handle (chrome bar)
802
+ var handleMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.1, metalness: 0.8 });
803
+ var handle = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.3, 0.04), handleMat);
804
+ handle.position.set(doorW / 2 - 0.1, 1.1, -offD / 2 + 0.03);
805
+ group.add(handle);
806
+ // Store door ref for animation
807
+ S._managerDoor = door;
808
+ S._managerDoorOpen = 0; // 0=closed, 1=open (lerp target)
809
+ S._managerDoorLerp = 0;
810
+ S._managerDoorClosedZ = -offD / 2;
811
+
812
+ // Left glass wall
813
+ var leftWall = new THREE.Mesh(new THREE.PlaneGeometry(offD, wallH), clearGlass);
814
+ leftWall.position.set(-offW / 2, wallH / 2, 0);
815
+ leftWall.rotation.y = Math.PI / 2;
816
+ group.add(leftWall);
817
+ var frostLeftW = new THREE.Mesh(new THREE.PlaneGeometry(offD, 0.8), frostedGlass);
818
+ frostLeftW.position.set(-offW / 2 + 0.01, 1.2, 0);
819
+ frostLeftW.rotation.y = Math.PI / 2;
820
+ group.add(frostLeftW);
821
+
822
+ // Right glass wall
823
+ var rightWall = new THREE.Mesh(new THREE.PlaneGeometry(offD, wallH), clearGlass);
824
+ rightWall.position.set(offW / 2, wallH / 2, 0);
825
+ rightWall.rotation.y = -Math.PI / 2;
826
+ group.add(rightWall);
827
+ var frostRightW = new THREE.Mesh(new THREE.PlaneGeometry(offD, 0.8), frostedGlass);
828
+ frostRightW.position.set(offW / 2 - 0.01, 1.2, 0);
829
+ frostRightW.rotation.y = -Math.PI / 2;
830
+ group.add(frostRightW);
831
+
832
+ // Back glass wall
833
+ var backWall = new THREE.Mesh(new THREE.PlaneGeometry(offW, wallH), clearGlass);
834
+ backWall.position.set(0, wallH / 2, offD / 2);
835
+ backWall.rotation.y = Math.PI;
836
+ group.add(backWall);
837
+ var frostBackW = new THREE.Mesh(new THREE.PlaneGeometry(offW, 0.8), frostedGlass);
838
+ frostBackW.position.set(0, 1.2, offD / 2 - 0.01);
839
+ frostBackW.rotation.y = Math.PI;
840
+ group.add(frostBackW);
841
+
842
+ // --- Chrome frame structure ---
843
+ // Top beams (all 4 sides)
844
+ // Front beam
845
+ var beamFront = new THREE.Mesh(new THREE.BoxGeometry(offW, 0.06, 0.06), frameMat);
846
+ beamFront.position.set(0, wallH, -offD / 2); group.add(beamFront);
847
+ // Back beam
848
+ var beamBack = new THREE.Mesh(new THREE.BoxGeometry(offW, 0.06, 0.06), frameMat);
849
+ beamBack.position.set(0, wallH, offD / 2); group.add(beamBack);
850
+ // Left beam
851
+ var beamLeft = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.06, offD), frameMat);
852
+ beamLeft.position.set(-offW / 2, wallH, 0); group.add(beamLeft);
853
+ // Right beam
854
+ var beamRight = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.06, offD), frameMat);
855
+ beamRight.position.set(offW / 2, wallH, 0); group.add(beamRight);
856
+ // Vertical corner posts (all 4 corners)
857
+ [[-offW / 2, -offD / 2], [-offW / 2, offD / 2], [offW / 2, -offD / 2], [offW / 2, offD / 2]].forEach(function(p) {
858
+ var post = new THREE.Mesh(new THREE.BoxGeometry(0.06, wallH, 0.06), frameMat);
859
+ post.position.set(p[0], wallH / 2, p[1]);
860
+ group.add(post);
861
+ });
862
+ // Door frame posts
863
+ [-doorW / 2 - 0.03, doorW / 2 + 0.03].forEach(function(dx) {
864
+ var doorPost = new THREE.Mesh(new THREE.BoxGeometry(0.06, wallH, 0.06), frameMat);
865
+ doorPost.position.set(dx, wallH / 2, -offD / 2);
866
+ group.add(doorPost);
867
+ });
868
+ // Door top beam
869
+ var doorTopBeam = new THREE.Mesh(new THREE.BoxGeometry(doorW + 0.12, 0.06, 0.06), frameMat);
870
+ doorTopBeam.position.set(0, wallH, -offD / 2);
871
+ group.add(doorTopBeam);
872
+
873
+ // --- L-shaped executive desk (walnut + marble top) ---
874
+ var marbleTopMat = new THREE.MeshStandardMaterial({ color: 0xf0ece4, roughness: 0.12, metalness: 0.05 });
875
+ // Main section
876
+ var deskMain = new THREE.Mesh(new THREE.BoxGeometry(2.8, 0.06, 1.2), walnutMat);
877
+ deskMain.position.set(0, 0.78, 1.5); deskMain.castShadow = true;
878
+ group.add(deskMain);
879
+ var marbleTop1 = new THREE.Mesh(new THREE.BoxGeometry(2.82, 0.015, 1.22), marbleTopMat);
880
+ marbleTop1.position.set(0, 0.82, 1.5);
881
+ group.add(marbleTop1);
882
+ // Side wing
883
+ var deskWing = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.06, 0.8), walnutMat);
884
+ deskWing.position.set(1.6, 0.78, 0.7); deskWing.castShadow = true;
885
+ group.add(deskWing);
886
+ var marbleTop2 = new THREE.Mesh(new THREE.BoxGeometry(1.22, 0.015, 0.82), marbleTopMat);
887
+ marbleTop2.position.set(1.6, 0.82, 0.7);
888
+ group.add(marbleTop2);
889
+ // Desk legs (chrome, elegant)
890
+ [[-1.2, 1], [-1.2, 2], [1.2, 2], [1.2, 1], [2.1, 0.4], [2.1, 1]].forEach(function(p) {
891
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.78, 8), chromeMat);
892
+ leg.position.set(p[0], 0.39, p[1]);
893
+ group.add(leg);
894
+ });
895
+ // Cable management panel (dark, under desk back)
896
+ var cablePanelMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.5 });
897
+ var cablePanel = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.5, 0.04), cablePanelMat);
898
+ cablePanel.position.set(0, 0.5, 0.9);
899
+ group.add(cablePanel);
900
+
901
+ // --- Dual ultrawide monitors ---
902
+ var monMat = new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 });
903
+ [-0.45, 0.45].forEach(function(mx) {
904
+ var mon = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.35, 0.025), monMat);
905
+ mon.position.set(mx, 1.15, 1.05); mon.castShadow = true;
906
+ group.add(mon);
907
+ // Screen
908
+ var scrMat = new THREE.MeshStandardMaterial({ color: 0x1a2a4a, emissive: 0x58a6ff, emissiveIntensity: 0.3, roughness: 0.1 });
909
+ var scr = new THREE.Mesh(new THREE.PlaneGeometry(0.55, 0.3), scrMat);
910
+ scr.position.set(mx, 1.15, 1.037);
911
+ group.add(scr);
912
+ // Stand
913
+ var stand = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.22, 6), chromeMat);
914
+ stand.position.set(mx, 0.95, 1.05);
915
+ group.add(stand);
916
+ });
917
+ // Monitor arm (chrome, connecting both)
918
+ var monArm = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.03, 0.03), chromeMat);
919
+ monArm.position.set(0, 1.0, 1.05);
920
+ group.add(monArm);
921
+
922
+ // --- Keyboard + mouse ---
923
+ var kbMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.5 });
924
+ var kb = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.015, 0.12), kbMat);
925
+ kb.position.set(-0.15, 0.835, 1.8);
926
+ group.add(kb);
927
+ var mouse = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.015, 0.06), kbMat);
928
+ mouse.position.set(0.35, 0.835, 1.8);
929
+ group.add(mouse);
930
+
931
+ // --- Premium leather executive chair ---
932
+ var chairG = new THREE.Group();
933
+ chairG.position.set(0, 0, 2.4);
934
+ // 5-star base
935
+ var baseMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3, metalness: 0.4 });
936
+ for (var ci = 0; ci < 5; ci++) {
937
+ var ca = (ci / 5) * Math.PI * 2;
938
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.02, 0.035), baseMat);
939
+ arm.position.set(Math.cos(ca) * 0.16, 0.04, Math.sin(ca) * 0.16);
940
+ arm.rotation.y = -ca;
941
+ chairG.add(arm);
942
+ }
943
+ var cylM = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 0.4, 8), chromeMat);
944
+ cylM.position.y = 0.26; chairG.add(cylM);
945
+ // Wide seat
946
+ var seatM = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.1, 0.5), leatherMat);
947
+ seatM.position.y = 0.5; seatM.castShadow = true; chairG.add(seatM);
948
+ // Tall padded backrest
949
+ var backM = new THREE.Mesh(new THREE.BoxGeometry(0.48, 0.7, 0.08), leatherMat);
950
+ backM.position.set(0, 0.9, 0.24); backM.castShadow = true; chairG.add(backM);
951
+ // Headrest
952
+ var headM = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.12, 0.08), leatherMat);
953
+ headM.position.set(0, 1.3, 0.24); chairG.add(headM);
954
+ // Armrests
955
+ [-0.27, 0.27].forEach(function(ax) {
956
+ var armPost = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.25, 0.04), baseMat);
957
+ armPost.position.set(ax, 0.6, 0.08); chairG.add(armPost);
958
+ var armPad = new THREE.Mesh(new THREE.BoxGeometry(0.09, 0.03, 0.25), leatherMat);
959
+ armPad.position.set(ax, 0.73, 0.08); chairG.add(armPad);
960
+ });
961
+ group.add(chairG);
962
+
963
+ // --- Bookshelf (right wall, walnut) ---
964
+ var shelfGroup = new THREE.Group();
965
+ shelfGroup.position.set(3.2, 0, 0.5);
966
+ var shelfBack = new THREE.Mesh(new THREE.BoxGeometry(0.06, 2.2, 1.4), walnutMat);
967
+ shelfBack.position.y = 1.1; shelfBack.castShadow = true;
968
+ shelfGroup.add(shelfBack);
969
+ [0.05, 0.55, 1.1, 1.65, 2.15].forEach(function(sy) {
970
+ var shelf = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.03, 1.4), walnutMat);
971
+ shelf.position.set(0.12, sy, 0); shelf.receiveShadow = true;
972
+ shelfGroup.add(shelf);
973
+ });
974
+ // Books
975
+ var bookColors = [0xc0392b, 0x2980b9, 0x8e44ad, 0xd4a24e, 0x1abc9c, 0x2c3e50];
976
+ [0.09, 0.59, 1.14, 1.69].forEach(function(sy, si) {
977
+ var startZ = -0.55;
978
+ for (var bi2 = 0; bi2 < 5; bi2++) {
979
+ var bh = 0.32 + Math.sin(si + bi2) * 0.08;
980
+ var bw = 0.04 + Math.sin(si * 3 + bi2) * 0.015;
981
+ var bMat = new THREE.MeshStandardMaterial({ color: bookColors[(si * 3 + bi2) % bookColors.length], roughness: 0.8 });
982
+ var book = new THREE.Mesh(new THREE.BoxGeometry(0.18, bh, bw), bMat);
983
+ book.position.set(0.16, sy + bh / 2, startZ);
984
+ shelfGroup.add(book);
985
+ startZ += bw + 0.02;
986
+ }
987
+ });
988
+ group.add(shelfGroup);
989
+
990
+ // --- Small sofa + coffee table ---
991
+ var sofaMat = new THREE.MeshStandardMaterial({ color: 0x2a1a0a, roughness: 0.7 });
992
+ var sofaBase = new THREE.Mesh(new THREE.BoxGeometry(2, 0.3, 0.7), sofaMat);
993
+ sofaBase.position.set(-2.5, 0.18, -0.5); sofaBase.castShadow = true;
994
+ group.add(sofaBase);
995
+ var sofaBack = new THREE.Mesh(new THREE.BoxGeometry(2, 0.4, 0.15), sofaMat);
996
+ sofaBack.position.set(-2.5, 0.45, -0.85); sofaBack.castShadow = true;
997
+ group.add(sofaBack);
998
+ // Cushions
999
+ var cushionMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1a, roughness: 0.8 });
1000
+ [-3.1, -2.5, -1.9].forEach(function(cx) {
1001
+ var cushion = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.06, 0.55), cushionMat);
1002
+ cushion.position.set(cx, 0.36, -0.5);
1003
+ group.add(cushion);
1004
+ });
1005
+ // Coffee table (glass top, chrome legs)
1006
+ var coffeeGlassMat = new THREE.MeshStandardMaterial({ color: 0xccddee, transparent: true, opacity: 0.35, roughness: 0.05 });
1007
+ var coffeeTop = new THREE.Mesh(new THREE.BoxGeometry(1, 0.03, 0.5), coffeeGlassMat);
1008
+ coffeeTop.position.set(-2.5, 0.45, 0.2);
1009
+ group.add(coffeeTop);
1010
+ [-0.4, 0.4].forEach(function(lx) {
1011
+ [-0.18, 0.18].forEach(function(lz) {
1012
+ var cLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.42, 6), chromeMat);
1013
+ cLeg.position.set(-2.5 + lx, 0.22, 0.2 + lz);
1014
+ group.add(cLeg);
1015
+ });
1016
+ });
1017
+
1018
+ // --- Luxury plant ---
1019
+ var planterMat = new THREE.MeshStandardMaterial({ color: 0x2a2a3a, roughness: 0.5 });
1020
+ var planter = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.2, 0.5, 12), planterMat);
1021
+ planter.position.set(3, 0.25, -2.5); planter.castShadow = true;
1022
+ group.add(planter);
1023
+ var leafMat = new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.8 });
1024
+ for (var pi = 0; pi < 6; pi++) {
1025
+ var pa = (pi / 6) * Math.PI * 2;
1026
+ var leaf = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 6), leafMat);
1027
+ leaf.position.set(3 + Math.cos(pa) * 0.15, 0.6, -2.5 + Math.sin(pa) * 0.15);
1028
+ group.add(leaf);
1029
+ }
1030
+ var topL = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 6), leafMat);
1031
+ topL.position.set(3, 0.75, -2.5); group.add(topL);
1032
+
1033
+ // --- Gold accent artwork frame on back wall ---
1034
+ var goldMat = new THREE.MeshStandardMaterial({ color: 0xd4af37, roughness: 0.3, metalness: 0.7 });
1035
+ // Frame
1036
+ var artFrameTop = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.06, 0.06), goldMat);
1037
+ artFrameTop.position.set(0, 3.2, offD / 2 - 0.08); group.add(artFrameTop);
1038
+ var artFrameBot = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.06, 0.06), goldMat);
1039
+ artFrameBot.position.set(0, 2.0, offD / 2 - 0.08); group.add(artFrameBot);
1040
+ var artFrameL = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.26, 0.06), goldMat);
1041
+ artFrameL.position.set(-0.8, 2.6, offD / 2 - 0.08); group.add(artFrameL);
1042
+ var artFrameR = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.26, 0.06), goldMat);
1043
+ artFrameR.position.set(0.8, 2.6, offD / 2 - 0.08); group.add(artFrameR);
1044
+ // Canvas inside frame (dark elegant)
1045
+ var artMat = new THREE.MeshStandardMaterial({ color: 0x1a2a3a, roughness: 0.8 });
1046
+ var art = new THREE.Mesh(new THREE.PlaneGeometry(1.5, 1.1), artMat);
1047
+ art.position.set(0, 2.6, offD / 2 - 0.06);
1048
+ art.rotation.y = Math.PI;
1049
+ group.add(art);
1050
+
1051
+ // --- Warm ambient lighting ---
1052
+ var warmLight1 = new THREE.PointLight(0xffeedd, 0.4, 8);
1053
+ warmLight1.position.set(0, 3.5, 1.5);
1054
+ group.add(warmLight1);
1055
+ var warmLight2 = new THREE.PointLight(0xffeedd, 0.2, 5);
1056
+ warmLight2.position.set(-2, 2, 0);
1057
+ group.add(warmLight2);
1058
+
1059
+ // --- Pendant light (premium, gold accent) ---
1060
+ var pendWire = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.008, 2), new THREE.MeshStandardMaterial({ color: 0x333333 }));
1061
+ pendWire.position.set(0, wallH - 1, 1.5);
1062
+ group.add(pendWire);
1063
+ var pendShade = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.3, 0.2, 12, 1, true),
1064
+ new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.4, metalness: 0.3, side: THREE.DoubleSide }));
1065
+ pendShade.position.set(0, wallH - 2.1, 1.5);
1066
+ group.add(pendShade);
1067
+ var pendRim = new THREE.Mesh(new THREE.TorusGeometry(0.3, 0.01, 6, 24), goldMat);
1068
+ pendRim.position.set(0, wallH - 2.2, 1.5);
1069
+ pendRim.rotation.x = Math.PI / 2;
1070
+ group.add(pendRim);
1071
+
1072
+ // --- "MANAGER" gold sign above door ---
1073
+ var signDiv = document.createElement('div');
1074
+ signDiv.textContent = 'MANAGER';
1075
+ 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);';
1076
+ var sign = new CSS2DObject(signDiv);
1077
+ sign.position.set(0, wallH + 0.3, -offD / 2);
1078
+ group.add(sign);
1079
+
1080
+ S.furnitureGroup.add(group);
1081
+ S._managerOfficeGroup = group;
1082
+ S._managerOfficePos = { x: x, z: z };
1083
+
1084
+ // Register manager desk in deskMeshes so monitor screen system works
1085
+ var mgrDeskIdx = CAMPUS_DESKS.length - 1;
1086
+ var mgrScreenMat = new THREE.MeshStandardMaterial({ color: 0x333333, emissive: 0x333333, emissiveIntensity: 0.1, roughness: 0.2 });
1087
+ S.deskMeshes[mgrDeskIdx] = { group: group, screen: null, screenMat: mgrScreenMat, index: mgrDeskIdx, x: x, z: z + 1.7 };
1088
+ }
1089
+
1090
+ // ==================== DESIGNER STUDIO ====================
1091
+ function buildDesignerStudio(x, z, walnutMat, chromeMat) {
1092
+ // Mood board wall
1093
+ var boardMat = new THREE.MeshStandardMaterial({ color: 0x3a3a4a, roughness: 0.5 });
1094
+ var board = new THREE.Mesh(new THREE.BoxGeometry(0.08, 2, 4), boardMat);
1095
+ board.position.set(x - 5.5, 1.5, z);
1096
+ board.castShadow = true;
1097
+ S.furnitureGroup.add(board);
1098
+ // Colorful sticky notes on board
1099
+ var noteColors = [0xfbbf24, 0xf87171, 0x34d399, 0x60a5fa, 0xa78bfa, 0xfb923c];
1100
+ for (var ni = 0; ni < 12; ni++) {
1101
+ var noteMat = new THREE.MeshStandardMaterial({ color: noteColors[ni % noteColors.length], roughness: 0.9 });
1102
+ var note = new THREE.Mesh(new THREE.PlaneGeometry(0.3, 0.3), noteMat);
1103
+ note.position.set(x - 5.44, 0.8 + Math.floor(ni / 4) * 0.5, z - 1.5 + (ni % 4) * 0.8);
1104
+ note.rotation.y = Math.PI / 2;
1105
+ S.furnitureGroup.add(note);
1106
+ }
1107
+
1108
+ // Standing desk
1109
+ var standDesk = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.06, 0.8), walnutMat);
1110
+ standDesk.position.set(x - 2, 1.1, z + 3);
1111
+ standDesk.castShadow = true;
1112
+ S.furnitureGroup.add(standDesk);
1113
+ // Adjustable legs
1114
+ [-0.7, 0.7].forEach(function(lx) {
1115
+ var standLeg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.1, 0.06), chromeMat);
1116
+ standLeg.position.set(x - 2 + lx, 0.55, z + 3);
1117
+ S.furnitureGroup.add(standLeg);
1118
+ });
1119
+
1120
+ // "DESIGN LAB" sign
1121
+ var signDiv = document.createElement('div');
1122
+ signDiv.textContent = 'DESIGN LAB';
1123
+ signDiv.style.cssText = 'color:#a855f7;font-size:9px;font-weight:bold;font-family:Inter,sans-serif;letter-spacing:2px;';
1124
+ var sign = new CSS2DObject(signDiv);
1125
+ sign.position.set(x, 3.5, z);
1126
+ S.furnitureGroup.add(sign);
1127
+ }
1128
+
1129
+ // ==================== BAR & CAFÉ ====================
1130
+ function buildBar(x, z, walnutMat, chromeMat, neonBlueMat, neonPurpleMat) {
1131
+ // Long bar counter
1132
+ var barTop = new THREE.Mesh(new THREE.BoxGeometry(6, 0.08, 1.2), walnutMat);
1133
+ barTop.position.set(x, 1.1, z); barTop.castShadow = true;
1134
+ S.furnitureGroup.add(barTop);
1135
+ var barFront = new THREE.Mesh(new THREE.BoxGeometry(6, 1.1, 0.1),
1136
+ new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.4 }));
1137
+ barFront.position.set(x, 0.55, z + 0.55);
1138
+ barFront.castShadow = true;
1139
+ S.furnitureGroup.add(barFront);
1140
+
1141
+ // LED strip under bar counter
1142
+ var barLed = new THREE.Mesh(new THREE.BoxGeometry(5.8, 0.02, 0.02), neonBlueMat);
1143
+ barLed.position.set(x, 1.02, z + 0.58);
1144
+ S.furnitureGroup.add(barLed);
1145
+
1146
+ // Bar stools (5)
1147
+ for (var si = 0; si < 5; si++) {
1148
+ var sx = x - 2 + si * 1;
1149
+ var stoolGroup = new THREE.Group();
1150
+ stoolGroup.position.set(sx, 0, z + 1.2);
1151
+ var stoolSeat = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.18, 0.06, 12),
1152
+ new THREE.MeshStandardMaterial({ color: 0x333340, roughness: 0.6 }));
1153
+ stoolSeat.position.y = 0.75;
1154
+ stoolGroup.add(stoolSeat);
1155
+ var stoolPost = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.7, 8), chromeMat);
1156
+ stoolPost.position.y = 0.38;
1157
+ stoolGroup.add(stoolPost);
1158
+ var stoolBase = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.2, 0.04, 12), chromeMat);
1159
+ stoolBase.position.y = 0.04;
1160
+ stoolGroup.add(stoolBase);
1161
+ S.furnitureGroup.add(stoolGroup);
1162
+ }
1163
+
1164
+ // Bottle shelf behind bar
1165
+ var shelfMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1a, roughness: 0.6 });
1166
+ [1.5, 2.2, 2.9].forEach(function(sy) {
1167
+ var shelf = new THREE.Mesh(new THREE.BoxGeometry(5.5, 0.04, 0.3), shelfMat);
1168
+ shelf.position.set(x, sy, z - 0.9);
1169
+ S.furnitureGroup.add(shelf);
1170
+ });
1171
+
1172
+ // Bottles on shelves
1173
+ var bottleColors = [0x2d8a4e, 0x8B4513, 0xd4af37, 0xcc3333, 0x1a5276, 0xf0f0f0];
1174
+ for (var bi = 0; bi < 15; bi++) {
1175
+ var bx = x - 2.5 + (bi % 5) * 1;
1176
+ var by = 1.55 + Math.floor(bi / 5) * 0.7;
1177
+ var bottleMat = new THREE.MeshStandardMaterial({ color: bottleColors[bi % bottleColors.length], roughness: 0.3, metalness: 0.1 });
1178
+ var bottle = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.25, 8), bottleMat);
1179
+ bottle.position.set(bx, by + 0.12, z - 0.85);
1180
+ S.furnitureGroup.add(bottle);
1181
+ var bottleNeck = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.03, 0.1, 6), bottleMat);
1182
+ bottleNeck.position.set(bx, by + 0.3, z - 0.85);
1183
+ S.furnitureGroup.add(bottleNeck);
1184
+ }
1185
+
1186
+ // Coffee machine
1187
+ var coffeeMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3, metalness: 0.2 });
1188
+ var coffee = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.5, 0.3), coffeeMat);
1189
+ coffee.position.set(x + 2.5, 1.4, z - 0.1);
1190
+ coffee.castShadow = true;
1191
+ S.furnitureGroup.add(coffee);
1192
+
1193
+ // "BAR" neon sign
1194
+ var signDiv = document.createElement('div');
1195
+ signDiv.textContent = 'BAR & CAFÉ';
1196
+ 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;';
1197
+ var sign = new CSS2DObject(signDiv);
1198
+ sign.position.set(x, 3.5, z - 1);
1199
+ S.furnitureGroup.add(sign);
1200
+ }
1201
+
1202
+ // ==================== RECREATION CENTER ====================
1203
+ function buildRecCenter(x, z, walnutMat, chromeMat, carpetMat) {
1204
+ // Carpet area
1205
+ var recCarpet = new THREE.Mesh(new THREE.PlaneGeometry(10, 8), carpetMat);
1206
+ recCarpet.rotation.x = -Math.PI / 2;
1207
+ recCarpet.position.set(x, 0.01, z);
1208
+ recCarpet.receiveShadow = true;
1209
+ S.furnitureGroup.add(recCarpet);
1210
+
1211
+ // Pool table
1212
+ var ptGroup = new THREE.Group();
1213
+ ptGroup.position.set(x - 2, 0, z);
1214
+ var ptTop = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.1, 1.3),
1215
+ new THREE.MeshStandardMaterial({ color: 0x006633, roughness: 0.9 }));
1216
+ ptTop.position.y = 0.85; ptTop.castShadow = true;
1217
+ ptGroup.add(ptTop);
1218
+ var ptFrame = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.15, 1.4), walnutMat);
1219
+ ptFrame.position.y = 0.78; ptFrame.castShadow = true;
1220
+ ptGroup.add(ptFrame);
1221
+ // Legs
1222
+ [[-1.1, -0.55], [-1.1, 0.55], [1.1, -0.55], [1.1, 0.55]].forEach(function(p) {
1223
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.7, 8), walnutMat);
1224
+ leg.position.set(p[0], 0.35, p[1]);
1225
+ ptGroup.add(leg);
1226
+ });
1227
+ S.furnitureGroup.add(ptGroup);
1228
+
1229
+ // Foosball table
1230
+ var fbGroup = new THREE.Group();
1231
+ fbGroup.position.set(x + 2.5, 0, z);
1232
+ var fbBody = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.2, 0.75),
1233
+ new THREE.MeshStandardMaterial({ color: 0x2a1a0a, roughness: 0.6 }));
1234
+ fbBody.position.y = 0.85; fbBody.castShadow = true;
1235
+ fbGroup.add(fbBody);
1236
+ var fbField = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.02, 0.6),
1237
+ new THREE.MeshStandardMaterial({ color: 0x006633, roughness: 0.8 }));
1238
+ fbField.position.y = 0.96;
1239
+ fbGroup.add(fbField);
1240
+ // Legs
1241
+ [[-0.6, -0.3], [-0.6, 0.3], [0.6, -0.3], [0.6, 0.3]].forEach(function(p) {
1242
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.75, 6), chromeMat);
1243
+ leg.position.set(p[0], 0.38, p[1]);
1244
+ fbGroup.add(leg);
1245
+ });
1246
+ // Rods
1247
+ [-0.3, 0, 0.3].forEach(function(rz) {
1248
+ var rod = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.012, 0.9, 6), chromeMat);
1249
+ rod.position.set(0, 0.98, rz);
1250
+ rod.rotation.z = Math.PI / 2;
1251
+ fbGroup.add(rod);
1252
+ });
1253
+ S.furnitureGroup.add(fbGroup);
1254
+
1255
+ // Beanbags
1256
+ var bbColors = [0xe53e3e, 0x3b82f6, 0x22c55e, 0xa855f7];
1257
+ [{ 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) {
1258
+ var bbMat = new THREE.MeshStandardMaterial({ color: bbColors[bi], roughness: 0.9 });
1259
+ var bot = new THREE.Mesh(new THREE.SphereGeometry(0.45, 16, 12), bbMat);
1260
+ bot.position.set(x + bp.x, 0.22, z + bp.z);
1261
+ bot.scale.set(1, 0.5, 1);
1262
+ bot.castShadow = true;
1263
+ S.furnitureGroup.add(bot);
1264
+ });
1265
+
1266
+ // Static decorative TV (smaller, no dashboard — main TV is at reception)
1267
+ var tvMat2 = new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 });
1268
+ var tvBody = new THREE.Mesh(new THREE.BoxGeometry(2.5, 1.5, 0.08), tvMat2);
1269
+ tvBody.position.set(x, 2.3, z - 3.8);
1270
+ tvBody.castShadow = true;
1271
+ S.furnitureGroup.add(tvBody);
1272
+ var tvScr = new THREE.Mesh(new THREE.PlaneGeometry(2.3, 1.3),
1273
+ new THREE.MeshStandardMaterial({ color: 0x0a1520, emissive: 0x22c55e, emissiveIntensity: 0.15, roughness: 0.1 }));
1274
+ tvScr.position.set(x, 2.3, z - 3.75);
1275
+ S.furnitureGroup.add(tvScr);
1276
+
1277
+ // "REC ZONE" sign
1278
+ var signDiv = document.createElement('div');
1279
+ signDiv.textContent = 'REC ZONE';
1280
+ 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;';
1281
+ var sign = new CSS2DObject(signDiv);
1282
+ sign.position.set(x, 4.5, z);
1283
+ S.furnitureGroup.add(sign);
1284
+ }
1285
+
1286
+ // ==================== GYM ====================
1287
+ function buildGym(x, z, chromeMat, darkMat) {
1288
+ // Rubber floor
1289
+ var rubberMat = new THREE.MeshStandardMaterial({ color: 0x2a2a2a, roughness: 0.95 });
1290
+ var gymFloor = new THREE.Mesh(new THREE.PlaneGeometry(8, 8), rubberMat);
1291
+ gymFloor.rotation.x = -Math.PI / 2;
1292
+ gymFloor.position.set(x, 0.01, z);
1293
+ gymFloor.receiveShadow = true;
1294
+ S.furnitureGroup.add(gymFloor);
1295
+
1296
+ // Treadmill
1297
+ var tmGroup = new THREE.Group();
1298
+ tmGroup.position.set(x - 1.5, 0, z - 2);
1299
+ var tmBase = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.15, 1.6), darkMat);
1300
+ tmBase.position.y = 0.1; tmBase.castShadow = true;
1301
+ tmGroup.add(tmBase);
1302
+ var tmBelt = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.02, 1.3),
1303
+ new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.8 }));
1304
+ tmBelt.position.y = 0.19;
1305
+ tmGroup.add(tmBelt);
1306
+ // Handles
1307
+ [-0.3, 0.3].forEach(function(hx) {
1308
+ var handle = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 1, 6), chromeMat);
1309
+ handle.position.set(hx, 0.7, -0.6);
1310
+ tmGroup.add(handle);
1311
+ });
1312
+ var console2 = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.25, 0.08), darkMat);
1313
+ console2.position.set(0, 1.1, -0.65);
1314
+ tmGroup.add(console2);
1315
+ S.furnitureGroup.add(tmGroup);
1316
+
1317
+ // Dumbbell rack
1318
+ var rackBase = new THREE.Mesh(new THREE.BoxGeometry(2, 0.8, 0.4), chromeMat);
1319
+ rackBase.position.set(x + 1.5, 0.4, z - 3);
1320
+ rackBase.castShadow = true;
1321
+ S.furnitureGroup.add(rackBase);
1322
+ // Dumbbells on rack
1323
+ for (var di = 0; di < 5; di++) {
1324
+ var dbMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5, metalness: 0.4 });
1325
+ var db = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.3, 8), dbMat);
1326
+ db.position.set(x + 0.7 + di * 0.35, 0.9, z - 3);
1327
+ db.rotation.z = Math.PI / 2;
1328
+ S.furnitureGroup.add(db);
1329
+ }
1330
+
1331
+ // Yoga mat area
1332
+ var yogaMat = new THREE.MeshStandardMaterial({ color: 0x7c3aed, roughness: 0.9 });
1333
+ var mat = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.02, 1.8), yogaMat);
1334
+ mat.position.set(x + 2, 0.02, z + 1);
1335
+ S.furnitureGroup.add(mat);
1336
+ var mat2 = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.02, 1.8),
1337
+ new THREE.MeshStandardMaterial({ color: 0x06b6d4, roughness: 0.9 }));
1338
+ mat2.position.set(x + 3, 0.02, z + 1);
1339
+ S.furnitureGroup.add(mat2);
1340
+
1341
+ // "FITNESS" sign
1342
+ var signDiv = document.createElement('div');
1343
+ signDiv.textContent = 'FITNESS';
1344
+ 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;';
1345
+ var sign = new CSS2DObject(signDiv);
1346
+ sign.position.set(x, 4, z);
1347
+ S.furnitureGroup.add(sign);
1348
+ }
1349
+
1350
+ // ==================== PLANTS ====================
1351
+ function buildCampusPlants() {
1352
+ var plantPositions = [
1353
+ [-20, 8], [20, 8], [-20, -5], [20, -5],
1354
+ [-8, 10], [8, 10], [-8, -8], [8, -8],
1355
+ [0, 10], [-15, 5], [15, 5],
1356
+ [-6, -10], [6, -10],
1357
+ ];
1358
+ plantPositions.forEach(function(pos) {
1359
+ buildLuxuryPlant(pos[0], pos[1]);
1360
+ });
1361
+
1362
+ // Indoor trees (taller, premium)
1363
+ [[-18, 0], [18, 0], [0, -7]].forEach(function(pos) {
1364
+ buildIndoorTree(pos[0], pos[1]);
1365
+ });
1366
+ }
1367
+
1368
+ function buildLuxuryPlant(x, z) {
1369
+ var group = new THREE.Group();
1370
+ group.position.set(x, 0, z);
1371
+ // Concrete planter
1372
+ var planterMat = new THREE.MeshStandardMaterial({ color: 0x4a4a5a, roughness: 0.6 });
1373
+ var planter = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.2, 0.5, 12), planterMat);
1374
+ planter.position.y = 0.25; planter.castShadow = true;
1375
+ group.add(planter);
1376
+ // Lush greenery
1377
+ var leafMat = new THREE.MeshStandardMaterial({ color: 0x2d8a4e, roughness: 0.8 });
1378
+ for (var i = 0; i < 6; i++) {
1379
+ var a = (i / 6) * Math.PI * 2;
1380
+ var leaf = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 6), leafMat);
1381
+ leaf.position.set(Math.cos(a) * 0.15, 0.6, Math.sin(a) * 0.15);
1382
+ leaf.castShadow = true;
1383
+ group.add(leaf);
1384
+ }
1385
+ var topLeaf = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 6), leafMat);
1386
+ topLeaf.position.y = 0.75;
1387
+ group.add(topLeaf);
1388
+ S.furnitureGroup.add(group);
1389
+ }
1390
+
1391
+ function buildIndoorTree(x, z) {
1392
+ var group = new THREE.Group();
1393
+ group.position.set(x, 0, z);
1394
+ // Large planter
1395
+ var planterMat = new THREE.MeshStandardMaterial({ color: 0x3a3a4a, roughness: 0.5 });
1396
+ var planter = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.35, 0.6, 12), planterMat);
1397
+ planter.position.y = 0.3; planter.castShadow = true;
1398
+ group.add(planter);
1399
+ // Trunk
1400
+ var trunkMat = new THREE.MeshStandardMaterial({ color: 0x5c3a1e, roughness: 0.8 });
1401
+ var trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 2.5, 8), trunkMat);
1402
+ trunk.position.y = 1.85; trunk.castShadow = true;
1403
+ group.add(trunk);
1404
+ // Canopy (layered spheres)
1405
+ var canopyMat = new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.85 });
1406
+ var canopyMat2 = new THREE.MeshStandardMaterial({ color: 0x2d8a4e, roughness: 0.85 });
1407
+ [{ y: 2.8, r: 0.6 }, { y: 3.2, r: 0.5 }, { y: 3.5, r: 0.35 }].forEach(function(c, ci) {
1408
+ var canopy = new THREE.Mesh(new THREE.SphereGeometry(c.r, 12, 10), ci % 2 === 0 ? canopyMat : canopyMat2);
1409
+ canopy.position.y = c.y;
1410
+ canopy.castShadow = true;
1411
+ group.add(canopy);
1412
+ });
1413
+ S.furnitureGroup.add(group);
1414
+ }
1415
+
1416
+ // ==================== PENDANT LIGHTS ====================
1417
+ function buildPendantLights() {
1418
+ var lightPositions = [
1419
+ [0, 2], [0, -1], [0, -4],
1420
+ [-4.5, 2], [-4.5, -1], [-4.5, -4],
1421
+ [4.5, 2], [4.5, -1], [4.5, -4],
1422
+ [-12, 1], [-12, -2],
1423
+ [12, 5],
1424
+ [-14, -12], [0, -12], [14, -12],
1425
+ ];
1426
+ lightPositions.forEach(function(pos) {
1427
+ buildPendantLight(pos[0], pos[1]);
1428
+ });
1429
+ }
1430
+
1431
+ function buildPendantLight(x, z) {
1432
+ var group = new THREE.Group();
1433
+ group.position.set(x, 0, z);
1434
+ // Wire
1435
+ var wireMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5 });
1436
+ var wire = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.008, 1.5, 4), wireMat);
1437
+ wire.position.y = WALL_H - 0.75;
1438
+ group.add(wire);
1439
+ // Shade (industrial style)
1440
+ var shadeMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.5, metalness: 0.3, side: THREE.DoubleSide });
1441
+ var shade = new THREE.Mesh(new THREE.ConeGeometry(0.25, 0.2, 12, 1, true), shadeMat);
1442
+ shade.position.y = WALL_H - 1.55;
1443
+ group.add(shade);
1444
+ // Warm light
1445
+ var light = new THREE.PointLight(0xffeedd, 0.25, 6);
1446
+ light.position.set(0, WALL_H - 1.7, 0);
1447
+ light.castShadow = false; // performance
1448
+ group.add(light);
1449
+ S.furnitureGroup.add(group);
1450
+ }
1451
+
1452
+ // ==================== GLASS PARTITIONS ====================
1453
+ function buildGlassPartitions(glassMat, frameMat) {
1454
+ // Between coder zone and rec area
1455
+ var partition1 = new THREE.Mesh(new THREE.PlaneGeometry(14, 2.5), glassMat);
1456
+ partition1.position.set(0, 1.25, -7);
1457
+ S.furnitureGroup.add(partition1);
1458
+ var frame1 = new THREE.Mesh(new THREE.BoxGeometry(14, 0.04, 0.04), frameMat);
1459
+ frame1.position.set(0, 2.5, -7);
1460
+ S.furnitureGroup.add(frame1);
1461
+
1462
+ // Between designer area and main
1463
+ var partition2 = new THREE.Mesh(new THREE.PlaneGeometry(10, 2.5), glassMat);
1464
+ partition2.position.set(-8, 1.25, 0);
1465
+ partition2.rotation.y = Math.PI / 2;
1466
+ S.furnitureGroup.add(partition2);
1467
+ }
1468
+
1469
+ // ==================== NEON SIGNS ====================
1470
+ function buildNeonSign(text, x, y, z, neonMat) {
1471
+ // Glow bar behind text
1472
+ var glowBar = new THREE.Mesh(new THREE.BoxGeometry(text.length * 0.4, 0.4, 0.04), neonMat);
1473
+ glowBar.position.set(x, y, z);
1474
+ S.furnitureGroup.add(glowBar);
1475
+ // CSS label
1476
+ var color = '#' + neonMat.color.getHexString();
1477
+ var div = document.createElement('div');
1478
+ div.textContent = text;
1479
+ 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 + ';';
1480
+ var label = new CSS2DObject(div);
1481
+ label.position.set(x, y, z + 0.05);
1482
+ S.furnitureGroup.add(label);
1483
+ }