Nexus Refinement: Vision Points & Narrative Expansion #44
120
app.js
120
app.js
@@ -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;
|
||||
}
|
||||
|
||||
19
index.html
19
index.html
@@ -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
106
style.css
@@ -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
37
vision.json
Normal 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"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user