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>
139 lines
5.9 KiB
JavaScript
139 lines
5.9 KiB
JavaScript
// modules/panels/sigil.js — Timmy sigil floor overlay
|
|
import * as THREE from 'three';
|
|
|
|
const SIGIL_CANVAS_SIZE = 512;
|
|
const SIGIL_RADIUS = 3.8;
|
|
|
|
let sigilMesh, sigilMat, sigilRing1, sigilRing2, sigilRing3;
|
|
let sigilRing1Mat, sigilRing2Mat, sigilRing3Mat, sigilLight;
|
|
|
|
function drawSigilCanvas() {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = SIGIL_CANVAS_SIZE;
|
|
canvas.height = SIGIL_CANVAS_SIZE;
|
|
const ctx = canvas.getContext('2d');
|
|
const cx = SIGIL_CANVAS_SIZE / 2;
|
|
const cy = SIGIL_CANVAS_SIZE / 2;
|
|
const r = cx * 0.88;
|
|
|
|
ctx.clearRect(0, 0, SIGIL_CANVAS_SIZE, SIGIL_CANVAS_SIZE);
|
|
|
|
const bgGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
|
|
bgGrad.addColorStop(0, 'rgba(0, 200, 255, 0.10)');
|
|
bgGrad.addColorStop(0.5, 'rgba(0, 100, 200, 0.04)');
|
|
bgGrad.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
|
ctx.fillStyle = bgGrad;
|
|
ctx.fillRect(0, 0, SIGIL_CANVAS_SIZE, SIGIL_CANVAS_SIZE);
|
|
|
|
function glowCircle(x, y, radius, color, alpha, lineW) {
|
|
ctx.save(); ctx.globalAlpha = alpha; ctx.strokeStyle = color;
|
|
ctx.lineWidth = lineW; ctx.shadowColor = color; ctx.shadowBlur = 12;
|
|
ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.stroke(); ctx.restore();
|
|
}
|
|
|
|
function hexagram(ox, oy, hr, color, alpha) {
|
|
ctx.save(); ctx.globalAlpha = alpha; ctx.strokeStyle = color;
|
|
ctx.lineWidth = 1.4; ctx.shadowColor = color; ctx.shadowBlur = 10;
|
|
ctx.beginPath();
|
|
for (let i = 0; i < 3; i++) {
|
|
const a = (i / 3) * Math.PI * 2 - Math.PI / 2;
|
|
i === 0 ? ctx.moveTo(ox + Math.cos(a) * hr, oy + Math.sin(a) * hr) : ctx.lineTo(ox + Math.cos(a) * hr, oy + Math.sin(a) * hr);
|
|
}
|
|
ctx.closePath(); ctx.stroke();
|
|
ctx.beginPath();
|
|
for (let i = 0; i < 3; i++) {
|
|
const a = (i / 3) * Math.PI * 2 + Math.PI / 2;
|
|
i === 0 ? ctx.moveTo(ox + Math.cos(a) * hr, oy + Math.sin(a) * hr) : ctx.lineTo(ox + Math.cos(a) * hr, oy + Math.sin(a) * hr);
|
|
}
|
|
ctx.closePath(); ctx.stroke(); ctx.restore();
|
|
}
|
|
|
|
const petalR = r * 0.32;
|
|
glowCircle(cx, cy, petalR, '#00ccff', 0.65, 1.0);
|
|
for (let i = 0; i < 6; i++) {
|
|
const a = (i / 6) * Math.PI * 2;
|
|
glowCircle(cx + Math.cos(a) * petalR, cy + Math.sin(a) * petalR, petalR, '#00aadd', 0.50, 0.8);
|
|
}
|
|
for (let i = 0; i < 6; i++) {
|
|
const a = (i / 6) * Math.PI * 2 + Math.PI / 6;
|
|
glowCircle(cx + Math.cos(a) * petalR * 1.73, cy + Math.sin(a) * petalR * 1.73, petalR, '#0077aa', 0.25, 0.6);
|
|
}
|
|
hexagram(cx, cy, r * 0.62, '#ffd700', 0.75);
|
|
hexagram(cx, cy, r * 0.41, '#ffaa00', 0.50);
|
|
glowCircle(cx, cy, r * 0.92, '#0055aa', 0.40, 0.8);
|
|
glowCircle(cx, cy, r * 0.72, '#0099cc', 0.38, 0.8);
|
|
glowCircle(cx, cy, r * 0.52, '#00ccff', 0.42, 0.9);
|
|
glowCircle(cx, cy, r * 0.18, '#ffd700', 0.65, 1.2);
|
|
|
|
ctx.save(); ctx.globalAlpha = 0.28; ctx.strokeStyle = '#00aaff';
|
|
ctx.lineWidth = 0.6; ctx.shadowColor = '#00aaff'; ctx.shadowBlur = 5;
|
|
for (let i = 0; i < 12; i++) {
|
|
const a = (i / 12) * Math.PI * 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx + Math.cos(a) * r * 0.18, cy + Math.sin(a) * r * 0.18);
|
|
ctx.lineTo(cx + Math.cos(a) * r * 0.91, cy + Math.sin(a) * r * 0.91);
|
|
ctx.stroke();
|
|
}
|
|
ctx.restore();
|
|
|
|
ctx.save(); ctx.fillStyle = '#00ffcc'; ctx.shadowColor = '#00ffcc'; ctx.shadowBlur = 9;
|
|
for (let i = 0; i < 12; i++) {
|
|
const a = (i / 12) * Math.PI * 2;
|
|
ctx.globalAlpha = i % 2 === 0 ? 0.80 : 0.50;
|
|
ctx.beginPath();
|
|
ctx.arc(cx + Math.cos(a) * r * 0.91, cy + Math.sin(a) * r * 0.91, i % 2 === 0 ? 4 : 2.5, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
ctx.restore();
|
|
|
|
ctx.save(); ctx.globalAlpha = 1.0; ctx.fillStyle = '#ffffff';
|
|
ctx.shadowColor = '#88ddff'; ctx.shadowBlur = 18;
|
|
ctx.beginPath(); ctx.arc(cx, cy, 5, 0, Math.PI * 2); ctx.fill(); ctx.restore();
|
|
|
|
return canvas;
|
|
}
|
|
|
|
export function init(scene) {
|
|
const sigilTexture = new THREE.CanvasTexture(drawSigilCanvas());
|
|
sigilMat = new THREE.MeshBasicMaterial({
|
|
map: sigilTexture, transparent: true, opacity: 0.80,
|
|
depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide,
|
|
});
|
|
sigilMesh = new THREE.Mesh(new THREE.CircleGeometry(SIGIL_RADIUS, 128), sigilMat);
|
|
sigilMesh.rotation.x = -Math.PI / 2;
|
|
sigilMesh.position.y = 0.010;
|
|
sigilMesh.userData.zoomLabel = 'Timmy Sigil';
|
|
scene.add(sigilMesh);
|
|
|
|
sigilRing1Mat = new THREE.MeshBasicMaterial({ color: 0x00ccff, transparent: true, opacity: 0.45, depthWrite: false, blending: THREE.AdditiveBlending });
|
|
sigilRing1 = new THREE.Mesh(new THREE.TorusGeometry(SIGIL_RADIUS * 0.965, 0.025, 6, 96), sigilRing1Mat);
|
|
sigilRing1.rotation.x = Math.PI / 2; sigilRing1.position.y = 0.012;
|
|
scene.add(sigilRing1);
|
|
|
|
sigilRing2Mat = new THREE.MeshBasicMaterial({ color: 0xffd700, transparent: true, opacity: 0.40, depthWrite: false, blending: THREE.AdditiveBlending });
|
|
sigilRing2 = new THREE.Mesh(new THREE.TorusGeometry(SIGIL_RADIUS * 0.62, 0.020, 6, 72), sigilRing2Mat);
|
|
sigilRing2.rotation.x = Math.PI / 2; sigilRing2.position.y = 0.013;
|
|
scene.add(sigilRing2);
|
|
|
|
sigilRing3Mat = new THREE.MeshBasicMaterial({ color: 0x00ffcc, transparent: true, opacity: 0.35, depthWrite: false, blending: THREE.AdditiveBlending });
|
|
sigilRing3 = new THREE.Mesh(new THREE.TorusGeometry(SIGIL_RADIUS * 0.78, 0.018, 6, 80), sigilRing3Mat);
|
|
sigilRing3.rotation.x = Math.PI / 2; sigilRing3.position.y = 0.011;
|
|
scene.add(sigilRing3);
|
|
|
|
sigilLight = new THREE.PointLight(0x0088ff, 0.4, 8);
|
|
sigilLight.position.set(0, 0.5, 0);
|
|
scene.add(sigilLight);
|
|
}
|
|
|
|
export function update(elapsed) {
|
|
sigilMesh.rotation.z = elapsed * 0.04;
|
|
sigilRing1.rotation.z = elapsed * 0.06;
|
|
sigilRing2.rotation.z = -elapsed * 0.10;
|
|
sigilRing3.rotation.z = elapsed * 0.08;
|
|
sigilMat.opacity = 0.65 + Math.sin(elapsed * 1.3) * 0.18;
|
|
sigilRing1Mat.opacity = 0.38 + Math.sin(elapsed * 0.9) * 0.14;
|
|
sigilRing2Mat.opacity = 0.32 + Math.sin(elapsed * 1.6 + 1.2) * 0.12;
|
|
sigilRing3Mat.opacity = 0.28 + Math.sin(elapsed * 0.7 + 2.4) * 0.10;
|
|
sigilLight.intensity = 0.30 + Math.sin(elapsed * 1.1) * 0.15;
|
|
}
|