[claude] World map overview mode — press Tab for bird's-eye view (#140) #167
34
app.js
34
app.js
@@ -119,6 +119,27 @@ document.addEventListener('mousemove', (e) => {
|
||||
mouseY = (e.clientY / window.innerHeight - 0.5) * 2;
|
||||
});
|
||||
|
||||
// === OVERVIEW MODE (Tab — bird's-eye view of the whole Nexus) ===
|
||||
let overviewMode = false;
|
||||
let overviewT = 0; // 0 = normal view, 1 = overview
|
||||
|
||||
const NORMAL_CAM = new THREE.Vector3(0, 0, 5);
|
||||
const OVERVIEW_CAM = new THREE.Vector3(0, 200, 0.1); // overhead; tiny Z offset avoids gimbal lock
|
||||
|
||||
const overviewIndicator = document.getElementById('overview-indicator');
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
overviewMode = !overviewMode;
|
||||
if (overviewMode) {
|
||||
overviewIndicator.classList.add('visible');
|
||||
} else {
|
||||
overviewIndicator.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// === RESIZE HANDLER ===
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
@@ -133,12 +154,19 @@ function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
const elapsed = clock.getElapsedTime();
|
||||
|
||||
// Slow auto-rotation
|
||||
// Smooth camera transition for overview mode
|
||||
const targetT = overviewMode ? 1 : 0;
|
||||
overviewT += (targetT - overviewT) * 0.04;
|
||||
camera.position.lerpVectors(NORMAL_CAM, OVERVIEW_CAM, overviewT);
|
||||
camera.lookAt(0, 0, 0);
|
||||
|
||||
// Slow auto-rotation — suppressed during overview so the map stays readable
|
||||
const rotationScale = 1 - overviewT;
|
||||
targetRotX += (mouseY * 0.3 - targetRotX) * 0.02;
|
||||
targetRotY += (mouseX * 0.3 - targetRotY) * 0.02;
|
||||
|
||||
stars.rotation.x = targetRotX + elapsed * 0.01;
|
||||
stars.rotation.y = targetRotY + elapsed * 0.015;
|
||||
stars.rotation.x = (targetRotX + elapsed * 0.01) * rotationScale;
|
||||
stars.rotation.y = (targetRotY + elapsed * 0.015) * rotationScale;
|
||||
|
||||
constellationLines.rotation.x = stars.rotation.x;
|
||||
constellationLines.rotation.y = stars.rotation.y;
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
<audio id="ambient-sound" src="ambient.mp3" loop></audio>
|
||||
</div>
|
||||
|
||||
<div id="overview-indicator">
|
||||
<span>MAP VIEW</span>
|
||||
<span class="overview-hint">[Tab] to exit</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
36
style.css
36
style.css
@@ -70,3 +70,39 @@ canvas {
|
||||
outline: 2px dashed yellow;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* === OVERVIEW MODE === */
|
||||
#overview-indicator {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: var(--color-primary);
|
||||
font-family: var(--font-body);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
pointer-events: none;
|
||||
z-index: 20;
|
||||
border: 1px solid var(--color-primary);
|
||||
padding: 4px 10px;
|
||||
background: rgba(0, 0, 8, 0.6);
|
||||
white-space: nowrap;
|
||||
animation: overview-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
#overview-indicator.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.overview-hint {
|
||||
margin-left: 12px;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@keyframes overview-pulse {
|
||||
0%, 100% { opacity: 0.7; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user