Files
the-nexus/modules/panels/heatmap.js
Perplexity Computer 675b61d65e
All checks were successful
CI / validate (pull_request) Successful in 14s
CI / auto-merge (pull_request) Successful in 0s
refactor: modularize app.js into ES module architecture
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>
2026-03-24 18:12:53 +00:00

82 lines
2.7 KiB
JavaScript

// modules/panels/heatmap.js — Commit heatmap floor overlay
import * as THREE from 'three';
import { state } from '../core/state.js';
import { HEATMAP_ZONES } from '../data/gitea.js';
import { GLASS_RADIUS } from '../terrain/island.js';
const HEATMAP_SIZE = 512;
const HEATMAP_ZONE_SPAN_RAD = Math.PI / 2;
const heatmapCanvas = document.createElement('canvas');
heatmapCanvas.width = HEATMAP_SIZE;
heatmapCanvas.height = HEATMAP_SIZE;
const heatmapTexture = new THREE.CanvasTexture(heatmapCanvas);
const heatmapMat = new THREE.MeshBasicMaterial({
map: heatmapTexture, transparent: true, opacity: 0.9,
depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide,
});
let heatmapMesh;
export function drawHeatmap() {
const ctx = heatmapCanvas.getContext('2d');
const cx = HEATMAP_SIZE / 2;
const cy = HEATMAP_SIZE / 2;
const r = cx * 0.96;
ctx.clearRect(0, 0, HEATMAP_SIZE, HEATMAP_SIZE);
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.clip();
for (const zone of HEATMAP_ZONES) {
const intensity = state.zoneIntensity[zone.name] || 0;
if (intensity < 0.01) continue;
const [rr, gg, bb] = zone.color;
const baseRad = zone.angleDeg * (Math.PI / 180);
const startRad = baseRad - HEATMAP_ZONE_SPAN_RAD / 2;
const endRad = baseRad + HEATMAP_ZONE_SPAN_RAD / 2;
const gx = cx + Math.cos(baseRad) * r * 0.55;
const gy = cy + Math.sin(baseRad) * r * 0.55;
const grad = ctx.createRadialGradient(gx, gy, 0, gx, gy, r * 0.75);
grad.addColorStop(0, `rgba(${rr},${gg},${bb},${0.65 * intensity})`);
grad.addColorStop(0.45, `rgba(${rr},${gg},${bb},${0.25 * intensity})`);
grad.addColorStop(1, `rgba(${rr},${gg},${bb},0)`);
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, startRad, endRad);
ctx.closePath();
ctx.fillStyle = grad;
ctx.fill();
if (intensity > 0.05) {
const labelX = cx + Math.cos(baseRad) * r * 0.62;
const labelY = cy + Math.sin(baseRad) * r * 0.62;
ctx.font = `bold ${Math.round(13 * intensity + 7)}px "Courier New", monospace`;
ctx.fillStyle = `rgba(${rr},${gg},${bb},${Math.min(intensity * 1.2, 0.9)})`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(zone.name, labelX, labelY);
}
}
ctx.restore();
heatmapTexture.needsUpdate = true;
}
export function init(scene) {
heatmapMesh = new THREE.Mesh(
new THREE.CircleGeometry(GLASS_RADIUS, 64),
heatmapMat
);
heatmapMesh.rotation.x = -Math.PI / 2;
heatmapMesh.position.y = 0.005;
heatmapMesh.userData.zoomLabel = 'Activity Heatmap';
scene.add(heatmapMesh);
}
export function update(elapsed) {
heatmapMat.opacity = 0.75 + Math.sin(elapsed * 0.6) * 0.2;
}