This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
token-gated-economy/the-matrix/js/main.js
Alexander Whitestone 281727b673
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
feat: add elite-tier Trust Panel vouch UI in Workshop sidebar
Adds a "Trust Panel" section inside the session panel (left sidebar)
that is only visible when the authenticated user has elite trust tier.
Users can paste an npub1... or hex pubkey and click "Vouch" to grant
+20 trust points to a newcomer via POST /api/identity/vouch.

- New vouch.js module handles tier check, pubkey decoding, event signing,
  and API call with proper error/success feedback
- Exported signEvent() from nostr-identity.js for reusable Nostr signing
- Panel hidden by default; shown only after /identity/me confirms elite tier

Fixes #50

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:51:32 -04:00

156 lines
4.3 KiB
JavaScript

import { initWorld, onWindowResize, disposeWorld } from './world.js';
import {
initAgents, updateAgents, getAgentCount,
disposeAgents, getAgentStates, applyAgentStates,
getTimmyGroup, applySlap, getCameraShakeStrength,
TIMMY_WORLD_POS,
} from './agents.js';
import { initEffects, updateEffects, disposeEffects } from './effects.js';
import { initUI, updateUI } from './ui.js';
import { initInteraction, disposeInteraction, registerSlapTarget } from './interaction.js';
import { initWebSocket, getConnectionState, getJobCount } from './websocket.js';
import { initPaymentPanel } from './payment.js';
import { initSessionPanel } from './session.js';
import { initVouchPanel } from './vouch.js';
import { initNostrIdentity } from './nostr-identity.js';
import { warmup as warmupEdgeWorker, onReady as onEdgeWorkerReady } from './edge-worker-client.js';
import { setEdgeWorkerReady } from './ui.js';
import { initTimmyId } from './timmy-id.js';
import { AGENT_DEFS } from './agent-defs.js';
import { initNavigation, updateNavigation, disposeNavigation } from './navigation.js';
import { initHudLabels, updateHudLabels, disposeHudLabels } from './hud-labels.js';
let running = false;
let canvas = null;
let _lastTime = performance.now();
function buildWorld(firstInit, stateSnapshot) {
const { scene, camera, renderer } = initWorld(canvas);
canvas = renderer.domElement;
initEffects(scene);
initAgents(scene);
if (stateSnapshot) applyAgentStates(stateSnapshot);
// Navigation replaces OrbitControls
initNavigation(camera, renderer);
initInteraction(camera, renderer);
registerSlapTarget(getTimmyGroup(), applySlap);
// AR floating labels
initHudLabels(camera, AGENT_DEFS, TIMMY_WORLD_POS);
if (firstInit) {
initUI();
initWebSocket(scene);
initPaymentPanel();
initSessionPanel();
initVouchPanel();
void initNostrIdentity('/api');
warmupEdgeWorker();
onEdgeWorkerReady(() => setEdgeWorkerReady());
void initTimmyId();
}
const ac = new AbortController();
window.addEventListener('resize', () => onWindowResize(camera, renderer), { signal: ac.signal });
let frameCount = 0;
let lastFpsTime = performance.now();
let currentFps = 0;
running = true;
function animate() {
if (!running) return;
requestAnimationFrame(animate);
const now = performance.now();
const deltaMs = now - _lastTime;
_lastTime = now;
frameCount++;
if (now - lastFpsTime >= 1000) {
currentFps = Math.round(frameCount * 1000 / (now - lastFpsTime));
frameCount = 0;
lastFpsTime = now;
}
// FPS navigation
updateNavigation(deltaMs);
updateEffects(now);
updateAgents(now);
updateUI({
fps: currentFps,
agentCount: getAgentCount(),
jobCount: getJobCount(),
connectionState: getConnectionState(),
});
// Camera shake
const shakeStr = getCameraShakeStrength();
let sx = 0, sy = 0;
if (shakeStr > 0) {
const mag = shakeStr * 0.22;
sx = (Math.random() - 0.5) * mag;
sy = (Math.random() - 0.5) * mag * 0.45;
camera.position.x += sx;
camera.position.y += sy;
}
renderer.render(scene, camera);
if (shakeStr > 0) {
camera.position.x -= sx;
camera.position.y -= sy;
}
// AR label positions (after render so NDC is current)
updateHudLabels(camera, renderer);
}
animate();
return { scene, renderer, ac };
}
function teardown({ scene, renderer, ac }) {
running = false;
ac.abort();
disposeNavigation();
disposeInteraction();
disposeHudLabels();
disposeEffects();
disposeAgents();
disposeWorld(renderer, scene);
}
function main() {
const $overlay = document.getElementById('webgl-recovery-overlay');
let handle = buildWorld(true, null);
canvas.addEventListener('webglcontextlost', event => {
event.preventDefault();
running = false;
if ($overlay) $overlay.style.display = 'flex';
});
canvas.addEventListener('webglcontextrestored', () => {
const snapshot = getAgentStates();
teardown(handle);
_lastTime = performance.now();
handle = buildWorld(false, snapshot);
if ($overlay) $overlay.style.display = 'none';
});
}
main();
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register(import.meta.env.BASE_URL + 'sw.js').catch(() => {});
});
}