refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
app.js: 5416 → 528 lines (entry point, animation loop, event wiring)
modules/state.js: shared mutable state object
modules/constants.js: color palette
modules/matrix-rain.js: matrix rain canvas effect
modules/scene-setup.js: scene, camera, renderer, lighting, stars
modules/platform.js: glass platform, perlin noise, floating island, clouds
modules/heatmap.js: commit heatmap
modules/sigil.js: Timmy sigil
modules/controls.js: mouse, overview, zoom, photo mode
modules/effects.js: energy beam, sovereignty meter, rune ring
modules/earth.js: holographic earth
modules/warp.js: warp tunnel, crystals, lightning
modules/dual-brain.js: dual-brain holographic panel
modules/audio.js: Web Audio, spatial, portal hums
modules/debug.js: debug mode, websocket, session export
modules/celebrations.js: easter egg, shockwave, fireworks
modules/portals.js: portal loading
modules/bookshelves.js: floating bookshelves, spine textures
modules/oath.js: The Oath interactive SOUL.md
modules/panels.js: agent status board, LoRA panel
modules/weather.js: weather system, portal health
modules/extras.js: gravity zones, speech, timelapse, bitcoin
Largest file: 528 lines (app.js). No file exceeds 1000.
All files pass node --check. No refactoring — mechanical split only.
2026-03-24 15:12:15 -04:00
|
|
|
|
// === ENERGY BEAM + SOVEREIGNTY METER + RUNE RING ===
|
|
|
|
|
|
import * as THREE from 'three';
|
|
|
|
|
|
import { NEXUS } from './constants.js';
|
|
|
|
|
|
import { scene } from './scene-setup.js';
|
|
|
|
|
|
import { S } from './state.js';
|
|
|
|
|
|
|
|
|
|
|
|
// === ENERGY BEAM ===
|
|
|
|
|
|
const ENERGY_BEAM_RADIUS = 0.2;
|
|
|
|
|
|
const ENERGY_BEAM_HEIGHT = 50;
|
|
|
|
|
|
const ENERGY_BEAM_Y = 0;
|
|
|
|
|
|
const ENERGY_BEAM_X = -10;
|
|
|
|
|
|
const ENERGY_BEAM_Z = -10;
|
|
|
|
|
|
|
|
|
|
|
|
const energyBeamGeometry = new THREE.CylinderGeometry(ENERGY_BEAM_RADIUS, ENERGY_BEAM_RADIUS * 2.5, ENERGY_BEAM_HEIGHT, 32, 16, true);
|
|
|
|
|
|
export const energyBeamMaterial = new THREE.MeshBasicMaterial({
|
|
|
|
|
|
color: NEXUS.colors.accent,
|
|
|
|
|
|
emissive: NEXUS.colors.accent,
|
|
|
|
|
|
emissiveIntensity: 0.8,
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: 0.6,
|
|
|
|
|
|
blending: THREE.AdditiveBlending,
|
|
|
|
|
|
side: THREE.DoubleSide,
|
|
|
|
|
|
depthWrite: false
|
|
|
|
|
|
});
|
|
|
|
|
|
const energyBeam = new THREE.Mesh(energyBeamGeometry, energyBeamMaterial);
|
|
|
|
|
|
energyBeam.position.set(ENERGY_BEAM_X, ENERGY_BEAM_Y + ENERGY_BEAM_HEIGHT / 2, ENERGY_BEAM_Z);
|
|
|
|
|
|
scene.add(energyBeam);
|
|
|
|
|
|
|
|
|
|
|
|
export function animateEnergyBeam() {
|
|
|
|
|
|
S.energyBeamPulse += 0.02;
|
|
|
|
|
|
const agentIntensity = S._activeAgentCount === 0 ? 0.1 : Math.min(0.1 + S._activeAgentCount * 0.3, 1.0);
|
|
|
|
|
|
const pulseEffect = Math.sin(S.energyBeamPulse) * 0.15 * agentIntensity;
|
|
|
|
|
|
energyBeamMaterial.opacity = agentIntensity * 0.6 + pulseEffect;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// === SOVEREIGNTY METER ===
|
|
|
|
|
|
export const sovereigntyGroup = new THREE.Group();
|
|
|
|
|
|
sovereigntyGroup.position.set(0, 3.8, 0);
|
|
|
|
|
|
|
|
|
|
|
|
const meterBgGeo = new THREE.TorusGeometry(1.6, 0.1, 8, 64);
|
|
|
|
|
|
const meterBgMat = new THREE.MeshBasicMaterial({ color: 0x0a1828, transparent: true, opacity: 0.5 });
|
|
|
|
|
|
sovereigntyGroup.add(new THREE.Mesh(meterBgGeo, meterBgMat));
|
|
|
|
|
|
|
|
|
|
|
|
function sovereigntyHexColor(score) {
|
|
|
|
|
|
if (score >= 80) return 0x00ff88;
|
|
|
|
|
|
if (score >= 40) return 0xffcc00;
|
|
|
|
|
|
return 0xff4444;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildScoreArcGeo(score) {
|
|
|
|
|
|
return new THREE.TorusGeometry(1.6, 0.1, 8, 64, (score / 100) * Math.PI * 2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const scoreArcMat = new THREE.MeshBasicMaterial({
|
|
|
|
|
|
color: sovereigntyHexColor(S.sovereigntyScore),
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: 0.9,
|
|
|
|
|
|
});
|
|
|
|
|
|
const scoreArcMesh = new THREE.Mesh(buildScoreArcGeo(S.sovereigntyScore), scoreArcMat);
|
|
|
|
|
|
scoreArcMesh.rotation.z = Math.PI / 2;
|
|
|
|
|
|
sovereigntyGroup.add(scoreArcMesh);
|
|
|
|
|
|
|
|
|
|
|
|
export const meterLight = new THREE.PointLight(sovereigntyHexColor(S.sovereigntyScore), 0.7, 6);
|
|
|
|
|
|
sovereigntyGroup.add(meterLight);
|
|
|
|
|
|
|
|
|
|
|
|
function buildMeterTexture(score, label, assessmentType) {
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
|
canvas.width = 256;
|
|
|
|
|
|
canvas.height = 128;
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
const hexStr = score >= 80 ? '#00ff88' : score >= 40 ? '#ffcc00' : '#ff4444';
|
|
|
|
|
|
ctx.clearRect(0, 0, 256, 128);
|
|
|
|
|
|
ctx.font = 'bold 52px "Courier New", monospace';
|
|
|
|
|
|
ctx.fillStyle = hexStr;
|
|
|
|
|
|
ctx.textAlign = 'center';
|
|
|
|
|
|
ctx.fillText(`${score}%`, 128, 50);
|
|
|
|
|
|
ctx.font = '16px "Courier New", monospace';
|
|
|
|
|
|
ctx.fillStyle = '#8899bb';
|
|
|
|
|
|
ctx.fillText(label.toUpperCase(), 128, 74);
|
|
|
|
|
|
ctx.font = '11px "Courier New", monospace';
|
|
|
|
|
|
ctx.fillStyle = '#445566';
|
|
|
|
|
|
ctx.fillText('SOVEREIGNTY', 128, 94);
|
|
|
|
|
|
ctx.font = '9px "Courier New", monospace';
|
|
|
|
|
|
ctx.fillStyle = '#334455';
|
|
|
|
|
|
ctx.fillText(assessmentType === 'MANUAL' ? 'MANUAL ASSESSMENT' : 'MANUAL ASSESSMENT', 128, 112);
|
|
|
|
|
|
return new THREE.CanvasTexture(canvas);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const meterSpriteMat = new THREE.SpriteMaterial({
|
|
|
|
|
|
map: buildMeterTexture(S.sovereigntyScore, S.sovereigntyLabel, 'MANUAL'),
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
depthWrite: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
const meterSprite = new THREE.Sprite(meterSpriteMat);
|
|
|
|
|
|
meterSprite.scale.set(3.2, 1.6, 1);
|
|
|
|
|
|
sovereigntyGroup.add(meterSprite);
|
|
|
|
|
|
|
|
|
|
|
|
scene.add(sovereigntyGroup);
|
|
|
|
|
|
sovereigntyGroup.traverse(obj => {
|
|
|
|
|
|
if (obj.isMesh || obj.isSprite) obj.userData.zoomLabel = 'Sovereignty Meter';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
export async function loadSovereigntyStatus() {
|
|
|
|
|
|
try {
|
revert: strip Manus damage (nostr, SovOS, gutted app.js) — restore clean split
Reverts to the state of cbfacdf (split app.js into 21 modules, <1000 lines each).
Removes: nostr.js, nostr-panel.js, SovOS.js, RESEARCH_DROP_456.md, core/, data/
Historical archive preserved in .historical/ and branch archive/manus-damage-2026-03-24
Refs #418, #452, #454
2026-03-24 18:18:44 -04:00
|
|
|
|
const res = await fetch('./sovereignty-status.json');
|
|
|
|
|
|
if (!res.ok) throw new Error('not found');
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
const score = Math.max(0, Math.min(100, typeof data.score === 'number' ? data.score : 85));
|
|
|
|
|
|
const label = typeof data.label === 'string' ? data.label : '';
|
|
|
|
|
|
S.sovereigntyScore = score;
|
|
|
|
|
|
S.sovereigntyLabel = label;
|
refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
app.js: 5416 → 528 lines (entry point, animation loop, event wiring)
modules/state.js: shared mutable state object
modules/constants.js: color palette
modules/matrix-rain.js: matrix rain canvas effect
modules/scene-setup.js: scene, camera, renderer, lighting, stars
modules/platform.js: glass platform, perlin noise, floating island, clouds
modules/heatmap.js: commit heatmap
modules/sigil.js: Timmy sigil
modules/controls.js: mouse, overview, zoom, photo mode
modules/effects.js: energy beam, sovereignty meter, rune ring
modules/earth.js: holographic earth
modules/warp.js: warp tunnel, crystals, lightning
modules/dual-brain.js: dual-brain holographic panel
modules/audio.js: Web Audio, spatial, portal hums
modules/debug.js: debug mode, websocket, session export
modules/celebrations.js: easter egg, shockwave, fireworks
modules/portals.js: portal loading
modules/bookshelves.js: floating bookshelves, spine textures
modules/oath.js: The Oath interactive SOUL.md
modules/panels.js: agent status board, LoRA panel
modules/weather.js: weather system, portal health
modules/extras.js: gravity zones, speech, timelapse, bitcoin
Largest file: 528 lines (app.js). No file exceeds 1000.
All files pass node --check. No refactoring — mechanical split only.
2026-03-24 15:12:15 -04:00
|
|
|
|
scoreArcMesh.geometry.dispose();
|
|
|
|
|
|
scoreArcMesh.geometry = buildScoreArcGeo(score);
|
|
|
|
|
|
const col = sovereigntyHexColor(score);
|
|
|
|
|
|
scoreArcMat.color.setHex(col);
|
|
|
|
|
|
meterLight.color.setHex(col);
|
|
|
|
|
|
if (meterSpriteMat.map) meterSpriteMat.map.dispose();
|
revert: strip Manus damage (nostr, SovOS, gutted app.js) — restore clean split
Reverts to the state of cbfacdf (split app.js into 21 modules, <1000 lines each).
Removes: nostr.js, nostr-panel.js, SovOS.js, RESEARCH_DROP_456.md, core/, data/
Historical archive preserved in .historical/ and branch archive/manus-damage-2026-03-24
Refs #418, #452, #454
2026-03-24 18:18:44 -04:00
|
|
|
|
const assessmentType = data.assessment_type || 'MANUAL';
|
refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
app.js: 5416 → 528 lines (entry point, animation loop, event wiring)
modules/state.js: shared mutable state object
modules/constants.js: color palette
modules/matrix-rain.js: matrix rain canvas effect
modules/scene-setup.js: scene, camera, renderer, lighting, stars
modules/platform.js: glass platform, perlin noise, floating island, clouds
modules/heatmap.js: commit heatmap
modules/sigil.js: Timmy sigil
modules/controls.js: mouse, overview, zoom, photo mode
modules/effects.js: energy beam, sovereignty meter, rune ring
modules/earth.js: holographic earth
modules/warp.js: warp tunnel, crystals, lightning
modules/dual-brain.js: dual-brain holographic panel
modules/audio.js: Web Audio, spatial, portal hums
modules/debug.js: debug mode, websocket, session export
modules/celebrations.js: easter egg, shockwave, fireworks
modules/portals.js: portal loading
modules/bookshelves.js: floating bookshelves, spine textures
modules/oath.js: The Oath interactive SOUL.md
modules/panels.js: agent status board, LoRA panel
modules/weather.js: weather system, portal health
modules/extras.js: gravity zones, speech, timelapse, bitcoin
Largest file: 528 lines (app.js). No file exceeds 1000.
All files pass node --check. No refactoring — mechanical split only.
2026-03-24 15:12:15 -04:00
|
|
|
|
meterSpriteMat.map = buildMeterTexture(score, label, assessmentType);
|
|
|
|
|
|
meterSpriteMat.needsUpdate = true;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// defaults already set
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadSovereigntyStatus();
|
|
|
|
|
|
|
|
|
|
|
|
// === RUNE RING ===
|
|
|
|
|
|
let RUNE_COUNT = 12;
|
|
|
|
|
|
const RUNE_RING_RADIUS = 7.0;
|
|
|
|
|
|
export const RUNE_RING_Y = 1.5;
|
|
|
|
|
|
export const RUNE_ORBIT_SPEED = 0.08;
|
|
|
|
|
|
|
|
|
|
|
|
const ELDER_FUTHARK = ['ᚠ','ᚢ','ᚦ','ᚨ','ᚱ','ᚲ','ᚷ','ᚹ','ᚺ','ᚾ','ᛁ','ᛃ'];
|
|
|
|
|
|
const RUNE_GLOW_COLORS = ['#00ffcc', '#ff44ff'];
|
|
|
|
|
|
|
|
|
|
|
|
function createRuneTexture(glyph, color) {
|
|
|
|
|
|
const W = 128, H = 128;
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
|
canvas.width = W;
|
|
|
|
|
|
canvas.height = H;
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, W, H);
|
|
|
|
|
|
ctx.shadowColor = color;
|
|
|
|
|
|
ctx.shadowBlur = 28;
|
|
|
|
|
|
ctx.font = 'bold 78px serif';
|
|
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
|
|
ctx.textAlign = 'center';
|
|
|
|
|
|
ctx.textBaseline = 'middle';
|
|
|
|
|
|
ctx.fillText(glyph, W / 2, H / 2);
|
|
|
|
|
|
|
|
|
|
|
|
return new THREE.CanvasTexture(canvas);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const runeOrbitRingGeo = new THREE.TorusGeometry(RUNE_RING_RADIUS, 0.03, 6, 64);
|
|
|
|
|
|
const runeOrbitRingMat = new THREE.MeshBasicMaterial({
|
|
|
|
|
|
color: 0x224466, transparent: true, opacity: 0.22,
|
|
|
|
|
|
});
|
|
|
|
|
|
const runeOrbitRingMesh = new THREE.Mesh(runeOrbitRingGeo, runeOrbitRingMat);
|
|
|
|
|
|
runeOrbitRingMesh.rotation.x = Math.PI / 2;
|
|
|
|
|
|
runeOrbitRingMesh.position.y = RUNE_RING_Y;
|
|
|
|
|
|
scene.add(runeOrbitRingMesh);
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Array<{sprite: THREE.Sprite, baseAngle: number, floatPhase: number, portalOnline: boolean}>} */
|
|
|
|
|
|
export const runeSprites = [];
|
|
|
|
|
|
|
|
|
|
|
|
// portals ref — set from portals module
|
|
|
|
|
|
let _portalsRef = [];
|
|
|
|
|
|
export function setPortalsRef(ref) { _portalsRef = ref; }
|
|
|
|
|
|
export function getPortalsRef() { return _portalsRef; }
|
|
|
|
|
|
|
|
|
|
|
|
export function rebuildRuneRing() {
|
|
|
|
|
|
for (const rune of runeSprites) {
|
|
|
|
|
|
scene.remove(rune.sprite);
|
|
|
|
|
|
if (rune.sprite.material.map) rune.sprite.material.map.dispose();
|
|
|
|
|
|
rune.sprite.material.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
runeSprites.length = 0;
|
|
|
|
|
|
|
|
|
|
|
|
const portalData = _portalsRef.length > 0 ? _portalsRef : null;
|
|
|
|
|
|
const count = portalData ? portalData.length : RUNE_COUNT;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
|
const glyph = ELDER_FUTHARK[i % ELDER_FUTHARK.length];
|
|
|
|
|
|
const color = portalData ? portalData[i].color : RUNE_GLOW_COLORS[i % RUNE_GLOW_COLORS.length];
|
|
|
|
|
|
const isOnline = portalData ? portalData[i].status === 'online' : true;
|
|
|
|
|
|
const texture = createRuneTexture(glyph, color);
|
|
|
|
|
|
|
|
|
|
|
|
const runeMat = new THREE.SpriteMaterial({
|
|
|
|
|
|
map: texture,
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: isOnline ? 1.0 : 0.15,
|
|
|
|
|
|
depthWrite: false,
|
|
|
|
|
|
blending: THREE.AdditiveBlending,
|
|
|
|
|
|
});
|
|
|
|
|
|
const sprite = new THREE.Sprite(runeMat);
|
|
|
|
|
|
sprite.scale.set(1.3, 1.3, 1);
|
|
|
|
|
|
|
|
|
|
|
|
const baseAngle = (i / count) * Math.PI * 2;
|
|
|
|
|
|
sprite.position.set(
|
|
|
|
|
|
Math.cos(baseAngle) * RUNE_RING_RADIUS,
|
|
|
|
|
|
RUNE_RING_Y,
|
|
|
|
|
|
Math.sin(baseAngle) * RUNE_RING_RADIUS
|
|
|
|
|
|
);
|
|
|
|
|
|
scene.add(sprite);
|
|
|
|
|
|
runeSprites.push({ sprite, baseAngle, floatPhase: (i / count) * Math.PI * 2, portalOnline: isOnline });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rebuildRuneRing();
|