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>
91 lines
3.3 KiB
JavaScript
91 lines
3.3 KiB
JavaScript
// modules/panels/sovereignty.js — Sovereignty meter arc gauge
|
|
import * as THREE from 'three';
|
|
|
|
let sovereigntyGroup, scoreArcMesh, scoreArcMat, meterLight, meterSpriteMat;
|
|
let sovereigntyScore = 85;
|
|
let sovereigntyLabel = 'Mostly Sovereign';
|
|
|
|
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);
|
|
}
|
|
|
|
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('MANUAL ASSESSMENT', 128, 112);
|
|
return new THREE.CanvasTexture(canvas);
|
|
}
|
|
|
|
export function init(scene) {
|
|
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));
|
|
|
|
scoreArcMat = new THREE.MeshBasicMaterial({
|
|
color: sovereigntyHexColor(sovereigntyScore), transparent: true, opacity: 0.9,
|
|
});
|
|
scoreArcMesh = new THREE.Mesh(buildScoreArcGeo(sovereigntyScore), scoreArcMat);
|
|
scoreArcMesh.rotation.z = Math.PI / 2;
|
|
sovereigntyGroup.add(scoreArcMesh);
|
|
|
|
meterLight = new THREE.PointLight(sovereigntyHexColor(sovereigntyScore), 0.7, 6);
|
|
sovereigntyGroup.add(meterLight);
|
|
|
|
meterSpriteMat = new THREE.SpriteMaterial({
|
|
map: buildMeterTexture(sovereigntyScore, 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 function updateFromData(data) {
|
|
const score = Math.max(0, Math.min(100, typeof data.score === 'number' ? data.score : 85));
|
|
const label = typeof data.label === 'string' ? data.label : '';
|
|
sovereigntyScore = score;
|
|
sovereigntyLabel = label;
|
|
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();
|
|
const assessmentType = data.assessment_type || 'MANUAL';
|
|
meterSpriteMat.map = buildMeterTexture(score, label, assessmentType);
|
|
meterSpriteMat.needsUpdate = true;
|
|
}
|
|
|
|
export function update(elapsed) {
|
|
sovereigntyGroup.position.y = 3.8 + Math.sin(elapsed * 0.8) * 0.15;
|
|
meterLight.intensity = 0.5 + Math.sin(elapsed * 1.8) * 0.25;
|
|
}
|