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 <noreply@anthropic.com>
This commit is contained in:
151
app.js
151
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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user