Files
the-nexus/modules/oath.js
Alexander Whitestone 979c7cf96b
Some checks failed
Deploy Nexus / deploy (push) Failing after 5s
Staging Smoke Test / smoke-test (push) Successful in 1s
revert: strip Manus damage (nostr, SovOS, gutted app.js) — restore clean split
Reverts to the state of cbfacdf (split app.js into 21 modules, <1000 lines each).
Removes: nostr.js, nostr-panel.js, SovOS.js, RESEARCH_DROP_456.md, core/, data/
Historical archive preserved in .historical/ and branch archive/manus-damage-2026-03-24

Refs #418, #452, #454
2026-03-24 18:29:57 -04:00

146 lines
4.4 KiB
JavaScript

// === THE OATH ===
import * as THREE from 'three';
import { scene, camera, renderer, ambientLight, overheadLight } from './scene-setup.js';
import { S } from './state.js';
// Tome (3D trigger object)
export const tomeGroup = new THREE.Group();
tomeGroup.position.set(0, 5.8, 0);
tomeGroup.userData.zoomLabel = 'The Oath';
const tomeCoverMat = new THREE.MeshStandardMaterial({
color: 0x2a1800, metalness: 0.15, roughness: 0.7,
emissive: new THREE.Color(0xffd700).multiplyScalar(0.04),
});
const tomePagesMat = new THREE.MeshStandardMaterial({ color: 0xd8ceb0, roughness: 0.9, metalness: 0.0 });
const tomeBody = new THREE.Mesh(new THREE.BoxGeometry(1.1, 0.1, 1.4), tomeCoverMat);
tomeGroup.add(tomeBody);
const tomePages = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.07, 1.28), tomePagesMat);
tomePages.position.set(0.02, 0, 0);
tomeGroup.add(tomePages);
const tomeSpiMat = new THREE.MeshStandardMaterial({ color: 0xffd700, metalness: 0.6, roughness: 0.4 });
const tomeSpine = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.12, 1.4), tomeSpiMat);
tomeSpine.position.set(-0.52, 0, 0);
tomeGroup.add(tomeSpine);
tomeGroup.traverse(o => {
if (o.isMesh) {
o.userData.zoomLabel = 'The Oath';
o.castShadow = true;
o.receiveShadow = true;
}
});
scene.add(tomeGroup);
export const tomeGlow = new THREE.PointLight(0xffd700, 0.4, 5);
tomeGlow.position.set(0, 5.4, 0);
scene.add(tomeGlow);
// Oath spotlight
export const oathSpot = new THREE.SpotLight(0xffd700, 0, 40, Math.PI / 7, 0.4, 1.2);
oathSpot.position.set(0, 22, 0);
oathSpot.target.position.set(0, 0, 0);
oathSpot.castShadow = true;
oathSpot.shadow.mapSize.set(1024, 1024);
oathSpot.shadow.camera.near = 1;
oathSpot.shadow.camera.far = 50;
oathSpot.shadow.bias = -0.002;
scene.add(oathSpot);
scene.add(oathSpot.target);
// Saved light levels
const AMBIENT_NORMAL = ambientLight.intensity;
const OVERHEAD_NORMAL = overheadLight.intensity;
export async function loadSoulMd() {
try {
const res = await fetch('SOUL.md');
if (!res.ok) throw new Error('not found');
const raw = await res.text();
return raw.split('\n').slice(1).map(l => l.replace(/^#+\s*/, ''));
} catch {
return ['I am Timmy.', '', 'I am sovereign.', '', 'This Nexus is my home.'];
}
}
function scheduleOathLines(lines, textEl) {
let idx = 0;
const INTERVAL_MS = 1400;
function revealNext() {
if (idx >= lines.length || !S.oathActive) return;
const line = lines[idx++];
const span = document.createElement('span');
span.classList.add('oath-line');
if (!line.trim()) {
span.classList.add('blank');
} else {
span.textContent = line;
}
textEl.appendChild(span);
S.oathRevealTimer = setTimeout(revealNext, line.trim() ? INTERVAL_MS : INTERVAL_MS * 0.4);
}
revealNext();
}
export async function enterOath() {
if (S.oathActive) return;
S.oathActive = true;
ambientLight.intensity = 0.04;
overheadLight.intensity = 0.0;
oathSpot.intensity = 4.0;
const overlay = document.getElementById('oath-overlay');
const textEl = document.getElementById('oath-text');
if (!overlay || !textEl) return;
textEl.textContent = '';
overlay.classList.add('visible');
if (!S.oathLines.length) S.oathLines = await loadSoulMd();
scheduleOathLines(S.oathLines, textEl);
}
export function exitOath() {
if (!S.oathActive) return;
S.oathActive = false;
if (S.oathRevealTimer !== null) {
clearTimeout(S.oathRevealTimer);
S.oathRevealTimer = null;
}
ambientLight.intensity = AMBIENT_NORMAL;
overheadLight.intensity = OVERHEAD_NORMAL;
oathSpot.intensity = 0;
const overlay = document.getElementById('oath-overlay');
if (overlay) overlay.classList.remove('visible');
}
export function initOathListeners() {
document.addEventListener('keydown', (e) => {
if (e.key === 'o' || e.key === 'O') {
if (S.oathActive) exitOath(); else enterOath();
}
if (e.key === 'Escape' && S.oathActive) exitOath();
});
// Double-click on tome triggers oath
renderer.domElement.addEventListener('dblclick', (e) => {
const mx = (e.clientX / window.innerWidth) * 2 - 1;
const my = -(e.clientY / window.innerHeight) * 2 + 1;
const tomeRay = new THREE.Raycaster();
tomeRay.setFromCamera(new THREE.Vector2(mx, my), camera);
const hits = tomeRay.intersectObjects(tomeGroup.children, true);
if (hits.length) {
if (S.oathActive) exitOath(); else enterOath();
}
});
// Pre-fetch so first open is instant
loadSoulMd().then(lines => { S.oathLines = lines; });
}