refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
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.
2026-03-24 15:12:15 -04:00
|
|
|
// === PORTALS ===
|
|
|
|
|
import * as THREE from 'three';
|
|
|
|
|
import { scene } from './scene-setup.js';
|
|
|
|
|
import { rebuildRuneRing, setPortalsRef } from './effects.js';
|
|
|
|
|
import { setPortalsRefAudio, startPortalHums } from './audio.js';
|
|
|
|
|
import { S } from './state.js';
|
2026-03-24 21:28:03 +00:00
|
|
|
import { fetchPortals as fetchPortalData } from './data/loaders.js';
|
refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
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.
2026-03-24 15:12:15 -04:00
|
|
|
|
|
|
|
|
export const portalGroup = new THREE.Group();
|
|
|
|
|
scene.add(portalGroup);
|
|
|
|
|
|
|
|
|
|
export let portals = [];
|
|
|
|
|
|
2026-04-05 13:26:46 -04:00
|
|
|
const atlasOverlay = document.getElementById('portal-atlas-overlay');
|
|
|
|
|
const atlasList = document.getElementById('portal-atlas-list');
|
|
|
|
|
const atlasToggle = document.getElementById('portal-atlas-toggle');
|
|
|
|
|
const atlasClose = document.getElementById('portal-atlas-close');
|
|
|
|
|
|
|
|
|
|
function renderPortalAtlas() {
|
|
|
|
|
if (!atlasList) return;
|
|
|
|
|
atlasList.innerHTML = portals.map((portal) => {
|
|
|
|
|
const env = portal.environment || 'unknown';
|
|
|
|
|
const readiness = portal.readiness_state || portal.status || 'unknown';
|
|
|
|
|
const access = portal.access_mode || 'unknown';
|
|
|
|
|
const actionLabel = portal.destination?.action_label || 'Inspect';
|
|
|
|
|
const url = portal.destination?.url || '#';
|
|
|
|
|
return `
|
|
|
|
|
<article class="portal-atlas-card" data-portal-id="${portal.id}">
|
|
|
|
|
<div class="portal-atlas-meta">
|
|
|
|
|
<span class="portal-atlas-name">${portal.name}</span>
|
|
|
|
|
<span class="portal-atlas-status status-${portal.status}">${portal.status}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="portal-atlas-description">${portal.description}</p>
|
|
|
|
|
<div class="portal-atlas-tags">
|
|
|
|
|
<span>${portal.portal_type || 'portal'}</span>
|
|
|
|
|
<span>${portal.world_category || 'world'}</span>
|
|
|
|
|
<span>${env}</span>
|
|
|
|
|
<span>${access}</span>
|
|
|
|
|
<span>${readiness}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="portal-atlas-footer">
|
|
|
|
|
<span class="portal-atlas-owner">Owner: ${portal.owner || 'unknown'}</span>
|
|
|
|
|
<a class="portal-atlas-link" href="${url}">${actionLabel}</a>
|
|
|
|
|
</div>
|
|
|
|
|
</article>
|
|
|
|
|
`;
|
|
|
|
|
}).join('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setAtlasOpen(open) {
|
|
|
|
|
if (!atlasOverlay) return;
|
|
|
|
|
atlasOverlay.classList.toggle('visible', open);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function initPortalAtlas() {
|
|
|
|
|
atlasToggle?.addEventListener('click', () => setAtlasOpen(!atlasOverlay?.classList.contains('visible')));
|
|
|
|
|
atlasClose?.addEventListener('click', () => setAtlasOpen(false));
|
|
|
|
|
document.addEventListener('keydown', (event) => {
|
|
|
|
|
if (event.key === 'g' || event.key === 'G') setAtlasOpen(!atlasOverlay?.classList.contains('visible'));
|
|
|
|
|
if (event.key === 'Escape') setAtlasOpen(false);
|
|
|
|
|
});
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
if (params.get('atlas') === '1') setAtlasOpen(true); // atlas=1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initPortalAtlas();
|
|
|
|
|
|
refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
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.
2026-03-24 15:12:15 -04:00
|
|
|
function createPortals() {
|
|
|
|
|
const portalGeo = new THREE.TorusGeometry(3.0, 0.2, 16, 100);
|
|
|
|
|
|
|
|
|
|
portals.forEach(portal => {
|
|
|
|
|
const isOnline = portal.status === 'online';
|
|
|
|
|
|
|
|
|
|
const portalMat = new THREE.MeshBasicMaterial({
|
|
|
|
|
color: new THREE.Color(portal.color).convertSRGBToLinear(),
|
|
|
|
|
transparent: true,
|
|
|
|
|
opacity: isOnline ? 0.7 : 0.15,
|
|
|
|
|
blending: THREE.AdditiveBlending,
|
|
|
|
|
side: THREE.DoubleSide,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const portalMesh = new THREE.Mesh(portalGeo, portalMat);
|
|
|
|
|
|
|
|
|
|
portalMesh.position.set(portal.position.x, portal.position.y + 0.5, portal.position.z);
|
|
|
|
|
portalMesh.rotation.y = portal.rotation.y;
|
|
|
|
|
portalMesh.rotation.x = Math.PI / 2;
|
|
|
|
|
|
|
|
|
|
portalMesh.name = `portal-${portal.id}`;
|
|
|
|
|
portalMesh.userData.destinationUrl = portal.destination?.url || null;
|
|
|
|
|
portalMesh.userData.portalColor = new THREE.Color(portal.color).convertSRGBToLinear();
|
|
|
|
|
|
|
|
|
|
portalGroup.add(portalMesh);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// rebuildGravityZones forward ref
|
|
|
|
|
let _rebuildGravityZonesFn = null;
|
|
|
|
|
export function setRebuildGravityZonesFn(fn) { _rebuildGravityZonesFn = fn; }
|
|
|
|
|
|
|
|
|
|
// runPortalHealthChecks forward ref
|
|
|
|
|
let _runPortalHealthChecksFn = null;
|
|
|
|
|
export function setRunPortalHealthChecksFn(fn) { _runPortalHealthChecksFn = fn; }
|
|
|
|
|
|
|
|
|
|
export async function loadPortals() {
|
|
|
|
|
try {
|
2026-03-24 21:28:03 +00:00
|
|
|
portals = await fetchPortalData();
|
refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
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.
2026-03-24 15:12:15 -04:00
|
|
|
console.log('Loaded portals:', portals);
|
|
|
|
|
setPortalsRef(portals);
|
|
|
|
|
setPortalsRefAudio(portals);
|
|
|
|
|
createPortals();
|
2026-04-05 13:26:46 -04:00
|
|
|
renderPortalAtlas();
|
refactor: split app.js (5416 lines) into 21 modules — hard cap 1000 lines/file
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.
2026-03-24 15:12:15 -04:00
|
|
|
rebuildRuneRing();
|
|
|
|
|
if (_rebuildGravityZonesFn) _rebuildGravityZonesFn();
|
|
|
|
|
startPortalHums();
|
|
|
|
|
if (_runPortalHealthChecksFn) _runPortalHealthChecksFn();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to load portals:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|