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.
217 lines
7.1 KiB
JavaScript
217 lines
7.1 KiB
JavaScript
// === SOVEREIGNTY EASTER EGG + SHOCKWAVE + FIREWORKS + MERGE FLASH ===
|
|
import * as THREE from 'three';
|
|
import { scene, starMaterial, constellationLines } from './scene-setup.js';
|
|
import { S } from './state.js';
|
|
import { clock } from './warp.js';
|
|
|
|
// === SOVEREIGNTY EASTER EGG ===
|
|
const SOVEREIGNTY_WORD = 'sovereignty';
|
|
|
|
const sovereigntyMsg = document.getElementById('sovereignty-msg');
|
|
|
|
export function triggerSovereigntyEasterEgg() {
|
|
const originalLineColor = constellationLines.material.color.getHex();
|
|
constellationLines.material.color.setHex(0xffd700);
|
|
constellationLines.material.opacity = 0.9;
|
|
|
|
const originalStarColor = starMaterial.color.getHex();
|
|
const originalStarOpacity = starMaterial.opacity;
|
|
starMaterial.color.setHex(0xffd700);
|
|
starMaterial.opacity = 1.0;
|
|
|
|
if (sovereigntyMsg) {
|
|
sovereigntyMsg.classList.remove('visible');
|
|
void sovereigntyMsg.offsetWidth;
|
|
sovereigntyMsg.classList.add('visible');
|
|
}
|
|
|
|
const startTime = performance.now();
|
|
const DURATION = 2500;
|
|
|
|
function fadeBack() {
|
|
const t = Math.min((performance.now() - startTime) / DURATION, 1);
|
|
const eased = t * t;
|
|
|
|
const goldR = 1.0, goldG = 0.843, goldB = 0;
|
|
const origColor = new THREE.Color(originalStarColor);
|
|
starMaterial.color.setRGB(
|
|
goldR + (origColor.r - goldR) * eased,
|
|
goldG + (origColor.g - goldG) * eased,
|
|
goldB + (origColor.b - goldB) * eased
|
|
);
|
|
starMaterial.opacity = 1.0 + (originalStarOpacity - 1.0) * eased;
|
|
|
|
const origLineColor = new THREE.Color(originalLineColor);
|
|
constellationLines.material.color.setRGB(
|
|
1.0 + (origLineColor.r - 1.0) * eased,
|
|
0.843 + (origLineColor.g - 0.843) * eased,
|
|
0 + origLineColor.b * eased
|
|
);
|
|
|
|
if (t < 1) {
|
|
requestAnimationFrame(fadeBack);
|
|
} else {
|
|
starMaterial.color.setHex(originalStarColor);
|
|
starMaterial.opacity = originalStarOpacity;
|
|
constellationLines.material.color.setHex(originalLineColor);
|
|
if (sovereigntyMsg) sovereigntyMsg.classList.remove('visible');
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(fadeBack);
|
|
}
|
|
|
|
// === SHOCKWAVE RIPPLE ===
|
|
const SHOCKWAVE_RING_COUNT = 3;
|
|
const SHOCKWAVE_MAX_RADIUS = 14;
|
|
export const SHOCKWAVE_DURATION = 2.5;
|
|
|
|
export const shockwaveRings = [];
|
|
|
|
export function triggerShockwave() {
|
|
const now = clock.getElapsedTime();
|
|
for (let i = 0; i < SHOCKWAVE_RING_COUNT; i++) {
|
|
const mat = new THREE.MeshBasicMaterial({
|
|
color: 0x00ffff, transparent: true, opacity: 0,
|
|
side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending,
|
|
});
|
|
const geo = new THREE.RingGeometry(0.9, 1.0, 64);
|
|
const mesh = new THREE.Mesh(geo, mat);
|
|
mesh.rotation.x = -Math.PI / 2;
|
|
mesh.position.y = 0.02;
|
|
scene.add(mesh);
|
|
shockwaveRings.push({ mesh, mat, startTime: now, delay: i * 0.35 });
|
|
}
|
|
}
|
|
|
|
// === FIREWORK CELEBRATION ===
|
|
const FIREWORK_COLORS = [0xff4466, 0xffaa00, 0x00ffaa, 0x4488ff, 0xff44ff, 0xffff44, 0x00ffff];
|
|
export const FIREWORK_BURST_PARTICLES = 80;
|
|
export const FIREWORK_BURST_DURATION = 2.2;
|
|
export const FIREWORK_GRAVITY = -5.0;
|
|
|
|
export const fireworkBursts = [];
|
|
|
|
function spawnFireworkBurst(origin, color) {
|
|
const now = clock.getElapsedTime();
|
|
const count = FIREWORK_BURST_PARTICLES;
|
|
const positions = new Float32Array(count * 3);
|
|
const origins = new Float32Array(count * 3);
|
|
const velocities = new Float32Array(count * 3);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const theta = Math.random() * Math.PI * 2;
|
|
const phi = Math.acos(2 * Math.random() - 1);
|
|
const speed = 2.5 + Math.random() * 3.5;
|
|
velocities[i * 3] = Math.sin(phi) * Math.cos(theta) * speed;
|
|
velocities[i * 3 + 1] = Math.sin(phi) * Math.sin(theta) * speed;
|
|
velocities[i * 3 + 2] = Math.cos(phi) * speed;
|
|
|
|
origins[i * 3] = origin.x;
|
|
origins[i * 3 + 1] = origin.y;
|
|
origins[i * 3 + 2] = origin.z;
|
|
positions[i * 3] = origin.x;
|
|
positions[i * 3 + 1] = origin.y;
|
|
positions[i * 3 + 2] = origin.z;
|
|
}
|
|
|
|
const geo = new THREE.BufferGeometry();
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
const mat = new THREE.PointsMaterial({
|
|
color, size: 0.35, sizeAttenuation: true,
|
|
transparent: true, opacity: 1.0,
|
|
blending: THREE.AdditiveBlending, depthWrite: false,
|
|
});
|
|
|
|
const points = new THREE.Points(geo, mat);
|
|
scene.add(points);
|
|
fireworkBursts.push({ points, geo, mat, origins, velocities, startTime: now });
|
|
}
|
|
|
|
export function triggerFireworks() {
|
|
const burstCount = 6;
|
|
for (let i = 0; i < burstCount; i++) {
|
|
const delay = i * 0.35;
|
|
setTimeout(() => {
|
|
const x = (Math.random() - 0.5) * 12;
|
|
const y = 8 + Math.random() * 6;
|
|
const z = (Math.random() - 0.5) * 12;
|
|
const color = FIREWORK_COLORS[Math.floor(Math.random() * FIREWORK_COLORS.length)];
|
|
spawnFireworkBurst(new THREE.Vector3(x, y, z), color);
|
|
}, delay * 1000);
|
|
}
|
|
}
|
|
|
|
export function triggerMergeFlash() {
|
|
triggerShockwave();
|
|
const originalLineColor = constellationLines.material.color.getHex();
|
|
constellationLines.material.color.setHex(0x00ffff);
|
|
constellationLines.material.opacity = 1.0;
|
|
|
|
const originalStarColor = starMaterial.color.getHex();
|
|
const originalStarOpacity = starMaterial.opacity;
|
|
starMaterial.color.setHex(0x00ffff);
|
|
starMaterial.opacity = 1.0;
|
|
|
|
const startTime = performance.now();
|
|
const DURATION = 2000;
|
|
|
|
function fadeBack() {
|
|
const t = Math.min((performance.now() - startTime) / DURATION, 1);
|
|
const eased = t * t;
|
|
|
|
const mergeR = 0.0, mergeG = 1.0, mergeB = 1.0;
|
|
const origStarColor = new THREE.Color(originalStarColor);
|
|
starMaterial.color.setRGB(
|
|
mergeR + (origStarColor.r - mergeR) * eased,
|
|
mergeG + (origStarColor.g - mergeG) * eased,
|
|
mergeB + (origStarColor.b - mergeB) * eased
|
|
);
|
|
starMaterial.opacity = 1.0 + (originalStarOpacity - 1.0) * eased;
|
|
|
|
const origLineColor = new THREE.Color(originalLineColor);
|
|
constellationLines.material.color.setRGB(
|
|
mergeR + (origLineColor.r - mergeR) * eased,
|
|
mergeG + (origLineColor.g - mergeG) * eased,
|
|
mergeB + (origLineColor.b - mergeB) * eased
|
|
);
|
|
constellationLines.material.opacity = 1.0 + (0.18 - 1.0) * eased;
|
|
|
|
if (t < 1) {
|
|
requestAnimationFrame(fadeBack);
|
|
} else {
|
|
starMaterial.color.setHex(originalStarColor);
|
|
starMaterial.opacity = originalStarOpacity;
|
|
constellationLines.material.color.setHex(originalLineColor);
|
|
constellationLines.material.opacity = 0.18;
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(fadeBack);
|
|
}
|
|
|
|
export function initSovereigntyEasterEgg() {
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
if (e.key.length !== 1) {
|
|
S.sovereigntyBuffer = '';
|
|
return;
|
|
}
|
|
|
|
S.sovereigntyBuffer += e.key.toLowerCase();
|
|
|
|
if (S.sovereigntyBuffer.length > SOVEREIGNTY_WORD.length) {
|
|
S.sovereigntyBuffer = S.sovereigntyBuffer.slice(-SOVEREIGNTY_WORD.length);
|
|
}
|
|
|
|
if (S.sovereigntyBuffer === SOVEREIGNTY_WORD) {
|
|
S.sovereigntyBuffer = '';
|
|
triggerSovereigntyEasterEgg();
|
|
}
|
|
|
|
if (S.sovereigntyBufferTimer) clearTimeout(S.sovereigntyBufferTimer);
|
|
S.sovereigntyBufferTimer = setTimeout(() => { S.sovereigntyBuffer = ''; }, 3000);
|
|
});
|
|
}
|