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.
190 lines
6.0 KiB
JavaScript
190 lines
6.0 KiB
JavaScript
// === HOLOGRAPHIC EARTH ===
|
|
import * as THREE from 'three';
|
|
import { NEXUS } from './constants.js';
|
|
import { scene } from './scene-setup.js';
|
|
|
|
export const EARTH_RADIUS = 2.8;
|
|
export const EARTH_Y = 20.0;
|
|
export const EARTH_ROTATION_SPEED = 0.035;
|
|
const EARTH_AXIAL_TILT = 23.4 * (Math.PI / 180);
|
|
|
|
export const earthGroup = new THREE.Group();
|
|
earthGroup.position.set(0, EARTH_Y, 0);
|
|
earthGroup.rotation.z = EARTH_AXIAL_TILT;
|
|
scene.add(earthGroup);
|
|
|
|
export const earthSurfaceMat = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
uTime: { value: 0.0 },
|
|
uOceanColor: { value: new THREE.Color(0x003d99) },
|
|
uLandColor: { value: new THREE.Color(0x1a5c2a) },
|
|
uGlowColor: { value: new THREE.Color(NEXUS.colors.accent) },
|
|
},
|
|
vertexShader: `
|
|
varying vec3 vNormal;
|
|
varying vec3 vWorldPos;
|
|
varying vec2 vUv;
|
|
void main() {
|
|
vNormal = normalize(normalMatrix * normal);
|
|
vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
vUv = uv;
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
}
|
|
`,
|
|
fragmentShader: `
|
|
uniform float uTime;
|
|
uniform vec3 uOceanColor;
|
|
uniform vec3 uLandColor;
|
|
uniform vec3 uGlowColor;
|
|
varying vec3 vNormal;
|
|
varying vec3 vWorldPos;
|
|
varying vec2 vUv;
|
|
|
|
vec3 _m3(vec3 x){ return x - floor(x*(1./289.))*289.; }
|
|
vec4 _m4(vec4 x){ return x - floor(x*(1./289.))*289.; }
|
|
vec4 _p4(vec4 x){ return _m4((x*34.+1.)*x); }
|
|
float snoise(vec3 v){
|
|
const vec2 C = vec2(1./6., 1./3.);
|
|
vec3 i = floor(v + dot(v, C.yyy));
|
|
vec3 x0 = v - i + dot(i, C.xxx);
|
|
vec3 g = step(x0.yzx, x0.xyz);
|
|
vec3 l = 1.0 - g;
|
|
vec3 i1 = min(g.xyz, l.zxy);
|
|
vec3 i2 = max(g.xyz, l.zxy);
|
|
vec3 x1 = x0 - i1 + C.xxx;
|
|
vec3 x2 = x0 - i2 + C.yyy;
|
|
vec3 x3 = x0 - 0.5;
|
|
i = _m3(i);
|
|
vec4 p = _p4(_p4(_p4(
|
|
i.z+vec4(0.,i1.z,i2.z,1.))+
|
|
i.y+vec4(0.,i1.y,i2.y,1.))+
|
|
i.x+vec4(0.,i1.x,i2.x,1.));
|
|
float n_ = .142857142857;
|
|
vec3 ns = n_*vec3(2.,0.,-1.)+vec3(0.,-.5,1.);
|
|
vec4 j = p - 49.*floor(p*ns.z*ns.z);
|
|
vec4 x_ = floor(j*ns.z);
|
|
vec4 y_ = floor(j - 7.*x_);
|
|
vec4 h = 1. - abs(x_*(2./7.)) - abs(y_*(2./7.));
|
|
vec4 b0 = vec4(x_.xy,y_.xy)*(2./7.);
|
|
vec4 b1 = vec4(x_.zw,y_.zw)*(2./7.);
|
|
vec4 s0 = floor(b0)*2.+1.; vec4 s1 = floor(b1)*2.+1.;
|
|
vec4 sh = -step(h, vec4(0.));
|
|
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
|
|
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
|
|
vec3 p0=vec3(a0.xy,h.x); vec3 p1=vec3(a0.zw,h.y);
|
|
vec3 p2=vec3(a1.xy,h.z); vec3 p3=vec3(a1.zw,h.w);
|
|
vec4 nm = max(0.6-vec4(dot(x0,x0),dot(x1,x1),dot(x2,x2),dot(x3,x3)),0.);
|
|
vec4 nr = 1.79284291400159-0.85373472095314*nm;
|
|
p0*=nr.x; p1*=nr.y; p2*=nr.z; p3*=nr.w;
|
|
nm = nm*nm;
|
|
return 42.*dot(nm*nm, vec4(dot(p0,x0),dot(p1,x1),dot(p2,x2),dot(p3,x3)));
|
|
}
|
|
|
|
void main() {
|
|
vec3 n = normalize(vNormal);
|
|
vec3 vd = normalize(cameraPosition - vWorldPos);
|
|
|
|
float lat = (vUv.y - 0.5) * 3.14159265;
|
|
float lon = vUv.x * 6.28318530;
|
|
vec3 sp = vec3(cos(lat)*cos(lon), sin(lat), cos(lat)*sin(lon));
|
|
|
|
float c = snoise(sp*1.8)*0.60
|
|
+ snoise(sp*3.6)*0.30
|
|
+ snoise(sp*7.2)*0.10;
|
|
float land = smoothstep(0.05, 0.30, c);
|
|
|
|
vec3 surf = mix(uOceanColor, uLandColor, land);
|
|
surf = mix(surf, uGlowColor * 0.45, 0.38);
|
|
|
|
float scan = 0.5 + 0.5*sin(vUv.y * 220.0 + uTime * 1.8);
|
|
scan = smoothstep(0.30, 0.70, scan) * 0.14;
|
|
|
|
float fresnel = pow(1.0 - max(dot(n, vd), 0.0), 4.0);
|
|
|
|
vec3 col = surf + scan*uGlowColor*0.9 + fresnel*uGlowColor*1.5;
|
|
float alpha = 0.48 + fresnel * 0.42;
|
|
|
|
gl_FragColor = vec4(col, alpha);
|
|
}
|
|
`,
|
|
transparent: true,
|
|
depthWrite: false,
|
|
side: THREE.FrontSide,
|
|
});
|
|
|
|
const earthSphere = new THREE.SphereGeometry(EARTH_RADIUS, 64, 32);
|
|
export const earthMesh = new THREE.Mesh(earthSphere, earthSurfaceMat);
|
|
earthMesh.userData.zoomLabel = 'Planet Earth';
|
|
earthGroup.add(earthMesh);
|
|
|
|
// Lat/lon grid lines
|
|
(function buildEarthGrid() {
|
|
const lineMat = new THREE.LineBasicMaterial({
|
|
color: 0x2266bb, transparent: true, opacity: 0.30,
|
|
});
|
|
const r = EARTH_RADIUS + 0.015;
|
|
const SEG = 64;
|
|
|
|
for (let lat = -60; lat <= 60; lat += 30) {
|
|
const phi = lat * (Math.PI / 180);
|
|
const pts = [];
|
|
for (let i = 0; i <= SEG; i++) {
|
|
const th = (i / SEG) * Math.PI * 2;
|
|
pts.push(new THREE.Vector3(
|
|
Math.cos(phi) * Math.cos(th) * r,
|
|
Math.sin(phi) * r,
|
|
Math.cos(phi) * Math.sin(th) * r
|
|
));
|
|
}
|
|
earthGroup.add(new THREE.Line(
|
|
new THREE.BufferGeometry().setFromPoints(pts), lineMat
|
|
));
|
|
}
|
|
|
|
for (let lon = 0; lon < 360; lon += 30) {
|
|
const th = lon * (Math.PI / 180);
|
|
const pts = [];
|
|
for (let i = 0; i <= SEG; i++) {
|
|
const phi = (i / SEG) * Math.PI - Math.PI / 2;
|
|
pts.push(new THREE.Vector3(
|
|
Math.cos(phi) * Math.cos(th) * r,
|
|
Math.sin(phi) * r,
|
|
Math.cos(phi) * Math.sin(th) * r
|
|
));
|
|
}
|
|
earthGroup.add(new THREE.Line(
|
|
new THREE.BufferGeometry().setFromPoints(pts), lineMat
|
|
));
|
|
}
|
|
})();
|
|
|
|
// Atmosphere shell
|
|
const atmMat = new THREE.MeshBasicMaterial({
|
|
color: 0x1144cc, transparent: true, opacity: 0.07,
|
|
side: THREE.BackSide, depthWrite: false, blending: THREE.AdditiveBlending,
|
|
});
|
|
earthGroup.add(new THREE.Mesh(
|
|
new THREE.SphereGeometry(EARTH_RADIUS * 1.14, 32, 16), atmMat
|
|
));
|
|
|
|
export const earthGlowLight = new THREE.PointLight(NEXUS.colors.accent, 0.4, 25);
|
|
earthGroup.add(earthGlowLight);
|
|
|
|
earthGroup.traverse(obj => {
|
|
if (obj.isMesh || obj.isLine) obj.userData.zoomLabel = 'Planet Earth';
|
|
});
|
|
|
|
// Tether beam
|
|
(function buildEarthTetherBeam() {
|
|
const pts = [
|
|
new THREE.Vector3(0, EARTH_Y - EARTH_RADIUS * 1.15, 0),
|
|
new THREE.Vector3(0, 0.5, 0),
|
|
];
|
|
const beamGeo = new THREE.BufferGeometry().setFromPoints(pts);
|
|
const beamMat = new THREE.LineBasicMaterial({
|
|
color: NEXUS.colors.accent, transparent: true, opacity: 0.08,
|
|
depthWrite: false, blending: THREE.AdditiveBlending,
|
|
});
|
|
scene.add(new THREE.Line(beamGeo, beamMat));
|
|
})();
|