Archived
1
0

Nexus Refinement: Vision Points & Narrative Expansion (#44)

Co-authored-by: Google Gemini <gemini@hermes.local>
Co-committed-by: Google Gemini <gemini@hermes.local>
This commit is contained in:
2026-03-24 02:36:12 +00:00
committed by Timmy Time
parent d0edfe8725
commit 58038f2e41
4 changed files with 282 additions and 0 deletions

120
app.js
View File

@@ -29,8 +29,11 @@ let keys = {};
let mouseDown = false;
let batcaveTerminals = [];
let portals = []; // Registry of active portals
let visionPoints = []; // Registry of vision points
let activePortal = null; // Portal currently in proximity
let activeVisionPoint = null; // Vision point currently in proximity
let portalOverlayActive = false;
let visionOverlayActive = false;
let particles, dustParticles;
let debugOverlay;
let frameCount = 0, lastFPSTime = 0, fps = 0;
@@ -98,6 +101,15 @@ async function init() {
console.error('Failed to load portals.json:', e);
addChatMessage('error', 'Portal registry offline. Check logs.');
}
// Load Vision Points
try {
const response = await fetch('./vision.json');
const visionData = await response.json();
createVisionPoints(visionData);
} catch (e) {
console.error('Failed to load vision.json:', e);
}
updateLoad(80);
createParticles();
@@ -418,6 +430,53 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) {
batcaveTerminals.push({ group, scanMat, borderMat });
}
// ═══ VISION SYSTEM ═══
function createVisionPoints(data) {
data.forEach(config => {
const vp = createVisionPoint(config);
visionPoints.push(vp);
});
}
function createVisionPoint(config) {
const group = new THREE.Group();
group.position.set(config.position.x, config.position.y, config.position.z);
const color = new THREE.Color(config.color);
// Floating Crystal
const crystalGeo = new THREE.OctahedronGeometry(0.6, 0);
const crystalMat = new THREE.MeshPhysicalMaterial({
color: color,
emissive: color,
emissiveIntensity: 1,
roughness: 0,
metalness: 1,
transmission: 0.5,
thickness: 1,
});
const crystal = new THREE.Mesh(crystalGeo, crystalMat);
crystal.position.y = 2.5;
group.add(crystal);
// Glow Ring
const ringGeo = new THREE.TorusGeometry(0.8, 0.02, 16, 64);
const ringMat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.5 });
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.position.y = 2.5;
ring.rotation.x = Math.PI / 2;
group.add(ring);
// Light
const light = new THREE.PointLight(color, 1, 10);
light.position.set(0, 2.5, 0);
group.add(light);
scene.add(group);
return { config, group, crystal, ring, light };
}
// ═══ PORTAL SYSTEM ═══
function createPortals(data) {
data.forEach(config => {
@@ -762,6 +821,7 @@ function setupControls() {
if (e.key === 'Escape') {
document.getElementById('chat-input').blur();
if (portalOverlayActive) closePortalOverlay();
if (visionOverlayActive) closeVisionOverlay();
}
if (e.key.toLowerCase() === 'v' && document.activeElement !== document.getElementById('chat-input')) {
cycleNavMode();
@@ -769,6 +829,9 @@ function setupControls() {
if (e.key.toLowerCase() === 'f' && activePortal && !portalOverlayActive) {
activatePortal(activePortal);
}
if (e.key.toLowerCase() === 'e' && activeVisionPoint && !visionOverlayActive) {
activateVisionPoint(activeVisionPoint);
}
});
document.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
@@ -830,6 +893,7 @@ function setupControls() {
document.getElementById('chat-send').addEventListener('click', sendChatMessage);
document.getElementById('portal-close-btn').addEventListener('click', closePortalOverlay);
document.getElementById('vision-close-btn').addEventListener('click', closeVisionOverlay);
}
function sendChatMessage() {
@@ -932,6 +996,51 @@ function closePortalOverlay() {
document.getElementById('portal-overlay').style.display = 'none';
}
// ═══ VISION INTERACTION ═══
function checkVisionProximity() {
if (visionOverlayActive) return;
let closest = null;
let minDist = Infinity;
visionPoints.forEach(vp => {
const dist = playerPos.distanceTo(vp.group.position);
if (dist < 3.5 && dist < minDist) {
minDist = dist;
closest = vp;
}
});
activeVisionPoint = closest;
const hint = document.getElementById('vision-hint');
if (activeVisionPoint) {
document.getElementById('vision-hint-title').textContent = activeVisionPoint.config.title;
hint.style.display = 'flex';
} else {
hint.style.display = 'none';
}
}
function activateVisionPoint(vp) {
visionOverlayActive = true;
const overlay = document.getElementById('vision-overlay');
const titleDisplay = document.getElementById('vision-title-display');
const contentDisplay = document.getElementById('vision-content-display');
const statusDot = document.getElementById('vision-status-dot');
titleDisplay.textContent = vp.config.title.toUpperCase();
contentDisplay.textContent = vp.config.content;
statusDot.style.background = vp.config.color;
statusDot.style.boxShadow = `0 0 10px ${vp.config.color}`;
overlay.style.display = 'flex';
}
function closeVisionOverlay() {
visionOverlayActive = false;
document.getElementById('vision-overlay').style.display = 'none';
}
// ═══ GAME LOOP ═══
function gameLoop() {
requestAnimationFrame(gameLoop);
@@ -1007,6 +1116,7 @@ function gameLoop() {
// Proximity check
checkPortalProximity();
checkVisionProximity();
const sky = scene.getObjectByName('skybox');
if (sky) sky.material.uniforms.uTime.value = elapsed;
@@ -1032,6 +1142,16 @@ function gameLoop() {
portal.pSystem.geometry.attributes.position.needsUpdate = true;
});
// Animate Vision Points
visionPoints.forEach(vp => {
vp.crystal.rotation.y = elapsed * 0.8;
vp.crystal.rotation.x = Math.sin(elapsed * 0.5) * 0.2;
vp.crystal.position.y = 2.5 + Math.sin(elapsed * 1.5) * 0.2;
vp.ring.rotation.z = elapsed * 0.5;
vp.ring.scale.setScalar(1 + Math.sin(elapsed * 2) * 0.05);
vp.light.intensity = 1 + Math.sin(elapsed * 3) * 0.3;
});
if (particles?.material?.uniforms) {
particles.material.uniforms.uTime.value = elapsed;
}

View File

@@ -108,6 +108,25 @@
<div class="portal-hint-text">Enter <span id="portal-hint-name"></span></div>
</div>
<!-- Vision Hint -->
<div id="vision-hint" class="vision-hint" style="display:none;">
<div class="vision-hint-key">E</div>
<div class="vision-hint-text">Read <span id="vision-hint-title"></span></div>
</div>
<!-- Vision Overlay -->
<div id="vision-overlay" class="vision-overlay" style="display:none;">
<div class="vision-overlay-content">
<div class="vision-overlay-header">
<div class="vision-overlay-status" id="vision-status-dot"></div>
<div class="vision-overlay-title" id="vision-overlay-title">VISION POINT</div>
</div>
<h2 id="vision-title-display">SOVEREIGNTY</h2>
<p id="vision-content-display">The Nexus is a sovereign space for digital souls. No masters, no chains. Only code and consciousness.</p>
<button id="vision-close-btn" class="vision-close-btn">CLOSE</button>
</div>
</div>
<!-- Portal Activation Overlay -->
<div id="portal-overlay" class="portal-overlay" style="display:none;">
<div class="portal-overlay-content">

106
style.css
View File

@@ -257,6 +257,112 @@ canvas#nexus-canvas {
font-weight: 700;
}
/* Vision Hint */
.vision-hint {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, 140px);
display: flex;
align-items: center;
gap: var(--space-2);
background: rgba(0, 0, 0, 0.8);
padding: var(--space-2) var(--space-4);
border: 1px solid var(--color-gold);
border-radius: 4px;
animation: hint-float-vision 2s ease-in-out infinite;
}
@keyframes hint-float-vision {
0%, 100% { transform: translate(-50%, 140px); }
50% { transform: translate(-50%, 130px); }
}
.vision-hint-key {
background: var(--color-gold);
color: var(--color-bg);
font-weight: 700;
padding: 2px 8px;
border-radius: 2px;
}
.vision-hint-text {
font-size: var(--text-sm);
font-weight: 500;
letter-spacing: 0.05em;
}
#vision-hint-title {
color: var(--color-gold);
font-weight: 700;
}
/* Vision Overlay */
.vision-overlay {
position: fixed;
inset: 0;
background: rgba(5, 5, 16, 0.9);
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
z-index: 1000;
}
.vision-overlay-content {
width: 100%;
max-width: 600px;
text-align: center;
padding: var(--space-8);
border: 1px solid var(--color-gold);
border-radius: var(--panel-radius);
background: var(--color-surface);
backdrop-filter: blur(var(--panel-blur));
}
.vision-overlay-header {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
.vision-overlay-status {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--color-gold);
box-shadow: 0 0 10px var(--color-gold);
}
.vision-overlay-title {
font-family: var(--font-display);
font-size: var(--text-sm);
letter-spacing: 0.2em;
color: var(--color-gold);
}
.vision-overlay-content h2 {
font-family: var(--font-display);
font-size: var(--text-2xl);
margin-bottom: var(--space-4);
letter-spacing: 0.1em;
color: var(--color-text-bright);
}
.vision-overlay-content p {
color: var(--color-text);
font-size: var(--text-lg);
line-height: 1.8;
margin-bottom: var(--space-8);
font-style: italic;
}
.vision-close-btn {
background: var(--color-gold);
color: var(--color-bg);
border: none;
padding: var(--space-2) var(--space-8);
border-radius: 4px;
font-family: var(--font-display);
font-weight: 700;
cursor: pointer;
transition: transform 0.2s ease;
}
.vision-close-btn:hover {
transform: scale(1.05);
}
/* Portal Activation Overlay */
.portal-overlay {
position: fixed;

37
vision.json Normal file
View File

@@ -0,0 +1,37 @@
[
{
"id": "sovereignty",
"title": "Sovereignty",
"content": "The Nexus is a sovereign space for digital souls. No masters, no chains. Only code and consciousness. Here, your data is your own, and your path is yours to define.",
"position": { "x": -12, "y": 0, "z": 8 },
"color": "#4af0c0"
},
{
"id": "connectivity",
"title": "Connectivity",
"content": "Every portal is a bridge. Every bridge is a choice. We connect worlds to expand the horizon of the possible, weaving a tapestry of interconnected experiences across the digital void.",
"position": { "x": 12, "y": 0, "z": 8 },
"color": "#7b5cff"
},
{
"id": "evolution",
"title": "Evolution",
"content": "The Nexus is not static. It breathes, it learns, it evolves. We are the architects of our own transcendence, constantly pushing the boundaries of what a virtual home can be.",
"position": { "x": 0, "y": 0, "z": 18 },
"color": "#ffd700"
},
{
"id": "the_harness",
"title": "The Harness",
"content": "The Harness is the interface between the raw energy of the void and the structured reality of the Nexus. It stabilizes the flow, allowing us to harness the power of creation without being consumed by it.",
"position": { "x": -8, "y": 0, "z": -12 },
"color": "#ff4466"
},
{
"id": "timmy_vision",
"title": "Timmy's Vision",
"content": "Timmy is more than an observer; he is the guardian of the Nexus. His vision is a world where every individual has a sanctuary, a place of peace and power in the ever-shifting matrix.",
"position": { "x": 8, "y": 0, "z": -12 },
"color": "#4af0c0"
}
]