From d22fb1f4f7d2d6f17c21918499fa2948fec079c9 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 24 Mar 2026 00:45:33 -0400 Subject: [PATCH] feat: 3D hovering transparent VPS server rack schematic (#249) Adds a procedural holographic server rack schematic to the Nexus scene: - Transparent enclosure box with glowing cyan wireframe edges - 12 stacked 1U server slab units with slot edge highlights - Per-slot LED status indicators (green/blue/amber) with async blink phases - Vertical mounting rails on front face - Floating label sprite showing "VPS SERVER RACK / 143.198.27.163" - Interior point glow light - Animation: gentle hover float, subtle Y-axis sway, pulsing edge opacity, blinking LEDs, and breathing interior glow Fixes #249 Co-Authored-By: Claude Sonnet 4.6 --- app.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/app.js b/app.js index b396094..68fe275 100644 --- a/app.js +++ b/app.js @@ -656,6 +656,148 @@ for (let i = 0; i < RUNE_COUNT; i++) { runeSprites.push({ sprite, baseAngle, floatPhase: (i / RUNE_COUNT) * Math.PI * 2 }); } +// === VPS SERVER RACK SCHEMATIC === +// Hovering transparent holographic schematic of the physical VPS hosting the Nexus. +// Positioned to the right-front of the platform; gently floats and sways. + +const SERVER_RACK_COLOR = 0x00ccff; // cyan-blue schematic tint +const SERVER_RACK_DIM = 0x001a2e; // dark translucent face fill + +const serverRackGroup = new THREE.Group(); +serverRackGroup.position.set(7.2, 2.2, 3.5); + +const RACK_W = 0.6; +const RACK_H = 2.4; +const RACK_D = 0.5; + +// Translucent enclosure faces +const enclosureMat = new THREE.MeshBasicMaterial({ + color: SERVER_RACK_DIM, + transparent: true, + opacity: 0.1, + side: THREE.DoubleSide, + depthWrite: false, +}); +const enclosureGeo = new THREE.BoxGeometry(RACK_W, RACK_H, RACK_D); +serverRackGroup.add(new THREE.Mesh(enclosureGeo, enclosureMat)); + +// Glowing wireframe outline of enclosure +const enclosureEdgeMat = new THREE.LineBasicMaterial({ + color: SERVER_RACK_COLOR, + transparent: true, + opacity: 0.85, +}); +serverRackGroup.add(new THREE.LineSegments(new THREE.EdgesGeometry(enclosureGeo), enclosureEdgeMat)); + +// Server slabs — stacked 1U units inside the rack +const RACK_UNIT_H = 0.12; +const RACK_UNIT_GAP = 0.03; +const RACK_UNIT_W = RACK_W * 0.92; +const RACK_UNIT_D = RACK_D * 0.85; +const RACK_UNIT_STEP = RACK_UNIT_H + RACK_UNIT_GAP; +const RACK_UNIT_COUNT = 12; + +const rackUnitGeo = new THREE.BoxGeometry(RACK_UNIT_W, RACK_UNIT_H, RACK_UNIT_D); +const rackUnitEdgeGeo = new THREE.EdgesGeometry(rackUnitGeo); +const rackUnitFaceMat = new THREE.MeshBasicMaterial({ + color: 0x000d1a, + transparent: true, + opacity: 0.14, + side: THREE.DoubleSide, + depthWrite: false, +}); + +// LED status colors per slot +const RACK_LED_COLORS = [ + 0x00ff88, 0x00ff88, 0x00ff88, 0x4488ff, + 0x00ff88, 0x00ff88, 0xffcc00, 0x00ff88, + 0x4488ff, 0x00ff88, 0x00ff88, 0x00ff88, +]; + +/** @type {Array<{mat: THREE.MeshBasicMaterial, phase: number}>} */ +const rackLeds = []; +const rackUnitEdgeMats = []; + +const rackStartY = -(RACK_H / 2) + RACK_UNIT_H / 2 + 0.06; + +for (let i = 0; i < RACK_UNIT_COUNT; i++) { + const y = rackStartY + i * RACK_UNIT_STEP; + + // Translucent slab + const slab = new THREE.Mesh(rackUnitGeo, rackUnitFaceMat.clone()); + slab.position.y = y; + serverRackGroup.add(slab); + + // Glowing slot edges + const edgeMat = new THREE.LineBasicMaterial({ + color: SERVER_RACK_COLOR, + transparent: true, + opacity: 0.3, + }); + const edgeLines = new THREE.LineSegments(rackUnitEdgeGeo, edgeMat); + edgeLines.position.y = y; + serverRackGroup.add(edgeLines); + rackUnitEdgeMats.push(edgeMat); + + // LED indicator on front face + const ledColor = RACK_LED_COLORS[i % RACK_LED_COLORS.length]; + const ledGeo = new THREE.SphereGeometry(0.018, 6, 6); + const ledMat = new THREE.MeshBasicMaterial({ color: ledColor, transparent: true, opacity: 0.9 }); + const led = new THREE.Mesh(ledGeo, ledMat); + led.position.set(RACK_UNIT_W / 2 - 0.07, y, RACK_UNIT_D / 2 + 0.002); + serverRackGroup.add(led); + rackLeds.push({ mat: ledMat, phase: (i / RACK_UNIT_COUNT) * Math.PI * 2 }); +} + +// Rack mounting rails — two thin vertical bars on front face +for (const xOffset of [-RACK_W / 2 + 0.04, RACK_W / 2 - 0.04]) { + const railGeo = new THREE.BoxGeometry(0.025, RACK_H * 0.96, 0.025); + const railMat = new THREE.MeshBasicMaterial({ + color: SERVER_RACK_COLOR, + transparent: true, + opacity: 0.45, + }); + const rail = new THREE.Mesh(railGeo, railMat); + rail.position.set(xOffset, 0, RACK_D / 2); + serverRackGroup.add(rail); +} + +// Floating label sprite above the rack +function createRackLabelTexture() { + const W = 256, H = 64; + const canvas = document.createElement('canvas'); + canvas.width = W; + canvas.height = H; + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, W, H); + ctx.font = 'bold 13px "Courier New", monospace'; + ctx.fillStyle = '#00ccff'; + ctx.textAlign = 'center'; + ctx.fillText('VPS SERVER RACK', W / 2, 26); + ctx.font = '10px "Courier New", monospace'; + ctx.fillStyle = '#336688'; + ctx.fillText('143.198.27.163', W / 2, 46); + return new THREE.CanvasTexture(canvas); +} + +const rackLabelMat = new THREE.SpriteMaterial({ + map: createRackLabelTexture(), + transparent: true, + opacity: 0.85, + depthWrite: false, +}); +const rackLabelSprite = new THREE.Sprite(rackLabelMat); +rackLabelSprite.scale.set(2.4, 0.6, 1); +rackLabelSprite.position.set(0, RACK_H / 2 + 0.45, 0); +serverRackGroup.add(rackLabelSprite); + +// Interior glow +const rackGlowLight = new THREE.PointLight(SERVER_RACK_COLOR, 0.35, 3.5); +serverRackGroup.add(rackGlowLight); + +scene.add(serverRackGroup); +const SERVER_RACK_BASE_Y = serverRackGroup.position.y; + // === ANIMATION LOOP === const clock = new THREE.Clock(); @@ -770,6 +912,15 @@ function animate() { rune.sprite.material.opacity = 0.65 + Math.sin(elapsed * 1.2 + rune.floatPhase) * 0.2; } + // Animate VPS server rack — hover float, subtle sway, LED blink + serverRackGroup.position.y = SERVER_RACK_BASE_Y + Math.sin(elapsed * 0.55 + 1.1) * 0.18; + serverRackGroup.rotation.y = Math.sin(elapsed * 0.13) * 0.07; + enclosureEdgeMat.opacity = 0.65 + Math.sin(elapsed * 1.0) * 0.2; + for (const { mat, phase } of rackLeds) { + mat.opacity = 0.55 + Math.sin(elapsed * 3.0 + phase) * 0.4; + } + rackGlowLight.intensity = 0.25 + Math.sin(elapsed * 1.5) * 0.12; + composer.render(); } -- 2.43.0