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>
130 lines
4.2 KiB
JavaScript
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';
|
|
});
|
|
}
|
|
});
|