Files
the-nexus/modules/ui.js
Alexander Whitestone 4f196a175f
Some checks failed
CI / validate (pull_request) Failing after 14s
CI / auto-merge (pull_request) Has been skipped
refactor: split app.js into ES modules (scene, effects, controls, ui)
Refs #143

- modules/scene.js  — NEXUS palette, scene/camera/renderer, lighting, EffectComposer, OrbitControls
- modules/effects.js — star field, constellation lines, glass platform, sovereignty meter, commit banners, agent status board
- modules/controls.js — mouse state, overview mode (Tab), photo mode (P), resize handler
- modules/ui.js — debug toggle, WebSocket client, sovereignty easter egg

app.js is now a lean orchestrator: imports all modules, runs the
asset-loading manager, and owns the animate() loop. All files pass
`node --check` and are well under the 500 KB file-size budget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:40:17 -04:00

130 lines
4.2 KiB
JavaScript

import * as THREE from 'three';
import { constellationLines, starMaterial } from './effects.js';
import { wsClient } from '../ws-client.js';
// === WEBSOCKET CLIENT ===
wsClient.connect();
window.addEventListener('player-joined', (/** @type {CustomEvent} */ event) => {
console.log('Player joined:', event.detail);
});
window.addEventListener('player-left', (/** @type {CustomEvent} */ event) => {
console.log('Player left:', event.detail);
});
window.addEventListener('chat-message', (/** @type {CustomEvent} */ event) => {
console.log('Chat message:', event.detail);
if (typeof event.detail?.text === 'string' && event.detail.text.toLowerCase().includes('sovereignty')) {
triggerSovereigntyEasterEgg();
}
});
window.addEventListener('beforeunload', () => {
wsClient.disconnect();
});
// === SOVEREIGNTY EASTER EGG ===
const SOVEREIGNTY_WORD = 'sovereignty';
let sovereigntyBuffer = '';
let sovereigntyBufferTimer = /** @type {ReturnType<typeof setTimeout>|null} */ (null);
const sovereigntyMsg = document.getElementById('sovereignty-msg');
/**
* Triggers the sovereignty Easter egg: stars pulse gold, message flashes.
*/
export function triggerSovereigntyEasterEgg() {
const originalLineColor = constellationLines.material.color.getHex();
constellationLines.material.color.setHex(0xffd700);
constellationLines.material.opacity = 0.9;
const originalStarColor = starMaterial.color.getHex();
const originalStarOpacity = starMaterial.opacity;
starMaterial.color.setHex(0xffd700);
starMaterial.opacity = 1.0;
if (sovereigntyMsg) {
sovereigntyMsg.classList.remove('visible');
void sovereigntyMsg.offsetWidth;
sovereigntyMsg.classList.add('visible');
}
const startTime = performance.now();
const DURATION = 2500;
function fadeBack() {
const t = Math.min((performance.now() - startTime) / DURATION, 1);
const eased = t * t;
const goldR = 1.0, goldG = 0.843, goldB = 0;
const origColor = new THREE.Color(originalStarColor);
starMaterial.color.setRGB(
goldR + (origColor.r - goldR) * eased,
goldG + (origColor.g - goldG) * eased,
goldB + (origColor.b - goldB) * eased
);
starMaterial.opacity = 1.0 + (originalStarOpacity - 1.0) * eased;
const origLineColor = new THREE.Color(originalLineColor);
constellationLines.material.color.setRGB(
1.0 + (origLineColor.r - 1.0) * eased,
0.843 + (origLineColor.g - 0.843) * eased,
0 + origLineColor.b * eased
);
if (t < 1) {
requestAnimationFrame(fadeBack);
} else {
starMaterial.color.setHex(originalStarColor);
starMaterial.opacity = originalStarOpacity;
constellationLines.material.color.setHex(originalLineColor);
if (sovereigntyMsg) sovereigntyMsg.classList.remove('visible');
}
}
requestAnimationFrame(fadeBack);
}
document.addEventListener('keydown', (e) => {
if (e.metaKey || e.ctrlKey || e.altKey) return;
if (e.key.length !== 1) {
sovereigntyBuffer = '';
return;
}
sovereigntyBuffer += e.key.toLowerCase();
if (sovereigntyBuffer.length > SOVEREIGNTY_WORD.length) {
sovereigntyBuffer = sovereigntyBuffer.slice(-SOVEREIGNTY_WORD.length);
}
if (sovereigntyBuffer === SOVEREIGNTY_WORD) {
sovereigntyBuffer = '';
triggerSovereigntyEasterEgg();
}
if (sovereigntyBufferTimer) clearTimeout(sovereigntyBufferTimer);
sovereigntyBufferTimer = setTimeout(() => { sovereigntyBuffer = ''; }, 3000);
});
// === DEBUG MODE ===
let debugMode = false;
document.getElementById('debug-toggle').addEventListener('click', () => {
debugMode = !debugMode;
document.getElementById('debug-toggle').style.backgroundColor = debugMode
? 'var(--color-text-muted)'
: 'var(--color-secondary)';
console.log(`Debug mode ${debugMode ? 'enabled' : 'disabled'}`);
if (debugMode) {
document.querySelectorAll('.collision-box').forEach((/** @type {HTMLElement} */ el) => el.style.outline = '2px solid red');
document.querySelectorAll('.light-source').forEach((/** @type {HTMLElement} */ el) => el.style.outline = '2px dashed yellow');
} else {
document.querySelectorAll('.collision-box, .light-source').forEach((/** @type {HTMLElement} */ el) => {
el.style.outline = 'none';
});
}
});