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>
102 lines
3.4 KiB
JavaScript
102 lines
3.4 KiB
JavaScript
// modules/terrain/stars.js — Star field + constellation lines
|
|
import * as THREE from 'three';
|
|
import { THEME } from '../core/theme.js';
|
|
import { state } from '../core/state.js';
|
|
|
|
const STAR_COUNT = 800;
|
|
const STAR_SPREAD = 400;
|
|
const CONSTELLATION_DISTANCE = 30;
|
|
|
|
const STAR_BASE_OPACITY = 0.3;
|
|
const STAR_PEAK_OPACITY = 1.0;
|
|
const STAR_PULSE_DECAY = 0.012;
|
|
|
|
const starPositions = [];
|
|
const starGeo = new THREE.BufferGeometry();
|
|
const posArray = new Float32Array(STAR_COUNT * 3);
|
|
const sizeArray = new Float32Array(STAR_COUNT);
|
|
|
|
for (let i = 0; i < STAR_COUNT; i++) {
|
|
const x = (Math.random() - 0.5) * STAR_SPREAD;
|
|
const y = (Math.random() - 0.5) * STAR_SPREAD;
|
|
const z = (Math.random() - 0.5) * STAR_SPREAD;
|
|
posArray[i * 3] = x;
|
|
posArray[i * 3 + 1] = y;
|
|
posArray[i * 3 + 2] = z;
|
|
sizeArray[i] = Math.random() * 2.5 + 0.5;
|
|
starPositions.push(new THREE.Vector3(x, y, z));
|
|
}
|
|
|
|
starGeo.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
|
|
starGeo.setAttribute('size', new THREE.BufferAttribute(sizeArray, 1));
|
|
|
|
export const starMaterial = new THREE.PointsMaterial({
|
|
color: THEME.colors.starCore,
|
|
size: 0.6,
|
|
sizeAttenuation: true,
|
|
transparent: true,
|
|
opacity: 0.9,
|
|
});
|
|
|
|
export const stars = new THREE.Points(starGeo, starMaterial);
|
|
|
|
function buildConstellationLines() {
|
|
const linePositions = [];
|
|
const MAX_CONNECTIONS_PER_STAR = 3;
|
|
const connectionCount = new Array(STAR_COUNT).fill(0);
|
|
|
|
for (let i = 0; i < STAR_COUNT; i++) {
|
|
if (connectionCount[i] >= MAX_CONNECTIONS_PER_STAR) continue;
|
|
const neighbors = [];
|
|
for (let j = i + 1; j < STAR_COUNT; j++) {
|
|
if (connectionCount[j] >= MAX_CONNECTIONS_PER_STAR) continue;
|
|
const dist = starPositions[i].distanceTo(starPositions[j]);
|
|
if (dist < CONSTELLATION_DISTANCE) {
|
|
neighbors.push({ j, dist });
|
|
}
|
|
}
|
|
neighbors.sort((a, b) => a.dist - b.dist);
|
|
const toConnect = neighbors.slice(0, MAX_CONNECTIONS_PER_STAR - connectionCount[i]);
|
|
for (const { j } of toConnect) {
|
|
linePositions.push(
|
|
starPositions[i].x, starPositions[i].y, starPositions[i].z,
|
|
starPositions[j].x, starPositions[j].y, starPositions[j].z
|
|
);
|
|
connectionCount[i]++;
|
|
connectionCount[j]++;
|
|
}
|
|
}
|
|
|
|
const lineGeo = new THREE.BufferGeometry();
|
|
lineGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(linePositions), 3));
|
|
const lineMat = new THREE.LineBasicMaterial({
|
|
color: THEME.colors.constellationLine,
|
|
transparent: true,
|
|
opacity: 0.18,
|
|
});
|
|
return new THREE.LineSegments(lineGeo, lineMat);
|
|
}
|
|
|
|
export const constellationLines = buildConstellationLines();
|
|
|
|
export function init(scene) {
|
|
scene.add(stars);
|
|
scene.add(constellationLines);
|
|
}
|
|
|
|
export function update(elapsed, delta, mouseX, mouseY, overviewT, photoMode) {
|
|
const rotationScale = photoMode ? 0 : (1 - overviewT);
|
|
|
|
stars.rotation.x = (mouseY * 0.3 + elapsed * 0.01) * rotationScale;
|
|
stars.rotation.y = (mouseX * 0.3 + elapsed * 0.015) * rotationScale;
|
|
|
|
if (state.starPulseIntensity > 0) {
|
|
state.starPulseIntensity = Math.max(0, state.starPulseIntensity - STAR_PULSE_DECAY);
|
|
}
|
|
starMaterial.opacity = STAR_BASE_OPACITY + (STAR_PEAK_OPACITY - STAR_BASE_OPACITY) * state.starPulseIntensity;
|
|
|
|
constellationLines.rotation.x = stars.rotation.x;
|
|
constellationLines.rotation.y = stars.rotation.y;
|
|
constellationLines.material.opacity = 0.12 + Math.sin(elapsed * 0.5) * 0.06;
|
|
}
|