Split the monolithic 5393-line app.js into 32 focused ES modules under modules/ with a thin ~330-line orchestrator. No bundler required — runs in-browser via import maps. Module structure: core/ — scene, ticker, state, theme, audio data/ — gitea, weather, bitcoin, loaders terrain/ — stars, clouds, island effects/ — matrix-rain, energy-beam, lightning, shockwave, rune-ring, gravity-zones panels/ — heatmap, sigil, sovereignty, dual-brain, batcave, earth, agent-board, lora-panel portals/ — portal-system, commit-banners narrative/ — bookshelves, oath, chat utils/ — perlin All files pass node --check. No new dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
3.6 KiB
JavaScript
101 lines
3.6 KiB
JavaScript
// modules/panels/batcave.js — Batcave workshop area with reflection probe
|
|
import * as THREE from 'three';
|
|
import { THEME } from '../core/theme.js';
|
|
|
|
let batcaveGroup, batcaveProbe, batcaveProbeTarget, batcaveLight;
|
|
let batcaveMetallicMats = [];
|
|
let batcaveProbeLastUpdate = -999;
|
|
|
|
export function init(scene) {
|
|
const BATCAVE_ORIGIN = new THREE.Vector3(-10, 0, -8);
|
|
batcaveGroup = new THREE.Group();
|
|
batcaveGroup.position.copy(BATCAVE_ORIGIN);
|
|
scene.add(batcaveGroup);
|
|
|
|
batcaveProbeTarget = new THREE.WebGLCubeRenderTarget(128, {
|
|
type: THREE.HalfFloatType,
|
|
generateMipmaps: true,
|
|
minFilter: THREE.LinearMipmapLinearFilter,
|
|
});
|
|
batcaveProbe = new THREE.CubeCamera(0.1, 80, batcaveProbeTarget);
|
|
batcaveProbe.position.set(0, 1.2, -1);
|
|
batcaveGroup.add(batcaveProbe);
|
|
|
|
const batcaveFloorMat = new THREE.MeshStandardMaterial({
|
|
color: 0x0d1520, metalness: 0.92, roughness: 0.08, envMapIntensity: 1.4,
|
|
});
|
|
const batcaveWallMat = new THREE.MeshStandardMaterial({
|
|
color: 0x0a1828, metalness: 0.85, roughness: 0.15,
|
|
emissive: new THREE.Color(THEME.colors.accent).multiplyScalar(0.03),
|
|
envMapIntensity: 1.2,
|
|
});
|
|
const batcaveConsoleMat = new THREE.MeshStandardMaterial({
|
|
color: 0x060e16, metalness: 0.95, roughness: 0.05, envMapIntensity: 1.6,
|
|
});
|
|
batcaveMetallicMats = [batcaveFloorMat, batcaveWallMat, batcaveConsoleMat];
|
|
|
|
const batcaveFloor = new THREE.Mesh(new THREE.BoxGeometry(6, 0.08, 6), batcaveFloorMat);
|
|
batcaveFloor.position.y = -0.04;
|
|
batcaveGroup.add(batcaveFloor);
|
|
|
|
const batcaveBackWall = new THREE.Mesh(new THREE.BoxGeometry(6, 3, 0.1), batcaveWallMat);
|
|
batcaveBackWall.position.set(0, 1.5, -3);
|
|
batcaveGroup.add(batcaveBackWall);
|
|
|
|
const batcaveLeftWall = new THREE.Mesh(new THREE.BoxGeometry(0.1, 3, 6), batcaveWallMat);
|
|
batcaveLeftWall.position.set(-3, 1.5, 0);
|
|
batcaveGroup.add(batcaveLeftWall);
|
|
|
|
const batcaveConsoleBase = new THREE.Mesh(new THREE.BoxGeometry(3, 0.7, 1.2), batcaveConsoleMat);
|
|
batcaveConsoleBase.position.set(0, 0.35, -1.5);
|
|
batcaveGroup.add(batcaveConsoleBase);
|
|
|
|
const batcaveScreenBezel = new THREE.Mesh(new THREE.BoxGeometry(2.6, 1.4, 0.06), batcaveConsoleMat);
|
|
batcaveScreenBezel.position.set(0, 1.4, -2.08);
|
|
batcaveScreenBezel.rotation.x = Math.PI * 0.08;
|
|
batcaveGroup.add(batcaveScreenBezel);
|
|
|
|
const batcaveScreenGlow = new THREE.Mesh(
|
|
new THREE.PlaneGeometry(2.2, 1.1),
|
|
new THREE.MeshBasicMaterial({
|
|
color: new THREE.Color(THEME.colors.accent).multiplyScalar(0.65),
|
|
transparent: true, opacity: 0.82,
|
|
})
|
|
);
|
|
batcaveScreenGlow.position.set(0, 1.4, -2.05);
|
|
batcaveScreenGlow.rotation.x = Math.PI * 0.08;
|
|
batcaveGroup.add(batcaveScreenGlow);
|
|
|
|
batcaveLight = new THREE.PointLight(THEME.colors.accent, 0.9, 14);
|
|
batcaveLight.position.set(0, 2.8, -1);
|
|
batcaveGroup.add(batcaveLight);
|
|
|
|
const batcaveCeilingStrip = new THREE.Mesh(
|
|
new THREE.BoxGeometry(4.2, 0.05, 0.14),
|
|
new THREE.MeshStandardMaterial({
|
|
color: THEME.colors.accent,
|
|
emissive: new THREE.Color(THEME.colors.accent),
|
|
emissiveIntensity: 1.1,
|
|
})
|
|
);
|
|
batcaveCeilingStrip.position.set(0, 2.95, -1.2);
|
|
batcaveGroup.add(batcaveCeilingStrip);
|
|
|
|
batcaveGroup.traverse(obj => {
|
|
if (obj.isMesh) obj.userData.zoomLabel = 'Batcave';
|
|
});
|
|
}
|
|
|
|
export function updateProbe(elapsed, renderer, scene) {
|
|
if (elapsed - batcaveProbeLastUpdate > 2.0) {
|
|
batcaveProbeLastUpdate = elapsed;
|
|
batcaveGroup.visible = false;
|
|
batcaveProbe.update(renderer, scene);
|
|
batcaveGroup.visible = true;
|
|
for (const mat of batcaveMetallicMats) {
|
|
mat.envMap = batcaveProbeTarget.texture;
|
|
mat.needsUpdate = true;
|
|
}
|
|
}
|
|
}
|