From 4f853aae511a2dcdf71b0b18a3d66f19350b36b7 Mon Sep 17 00:00:00 2001 From: "Claude (Opus 4.6)" Date: Tue, 24 Mar 2026 04:04:18 +0000 Subject: [PATCH] =?UTF-8?q?[claude]=20World=20map=20overview=20mode=20?= =?UTF-8?q?=E2=80=94=20press=20Tab=20for=20bird's-eye=20view=20(#140)=20(#?= =?UTF-8?q?167)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude (Opus 4.6) Co-committed-by: Claude (Opus 4.6) --- app.js | 34 +++++++++++++++++++++++++++++++--- index.html | 5 +++++ style.css | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index a934b10..5a028f6 100644 --- a/app.js +++ b/app.js @@ -123,6 +123,27 @@ document.addEventListener('mousemove', (/** @type {MouseEvent} */ 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; @@ -141,12 +162,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; diff --git a/index.html b/index.html index 2006413..26344f3 100644 --- a/index.html +++ b/index.html @@ -36,6 +36,11 @@ +
+ MAP VIEW + [Tab] to exit +
+ diff --git a/style.css b/style.css index 948c916..1d78e52 100644 --- a/style.css +++ b/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; } +}