diff --git a/app.js b/app.js index 3f9b2cd..8e5b37d 100644 --- a/app.js +++ b/app.js @@ -163,12 +163,16 @@ const platformFrameMat = new THREE.MeshStandardMaterial({ const platformRimGeo = new THREE.RingGeometry(4.7, 5.3, 64); const platformRim = new THREE.Mesh(platformRimGeo, platformFrameMat); platformRim.rotation.x = -Math.PI / 2; +platformRim.userData.inspectName = 'Glass Platform'; +platformRim.userData.inspectDesc = 'Central floating platform — the sovereign foundation of the Nexus.'; glassPlatformGroup.add(platformRim); // Raised border torus for visible 3-D thickness const borderTorusGeo = new THREE.TorusGeometry(5.0, 0.1, 6, 64); const borderTorus = new THREE.Mesh(borderTorusGeo, platformFrameMat); borderTorus.rotation.x = Math.PI / 2; +borderTorus.userData.inspectName = 'Platform Rim'; +borderTorus.userData.inspectDesc = 'Raised metallic torus encircling the Nexus platform.'; glassPlatformGroup.add(borderTorus); // Glass tile material — highly transmissive to reveal the void below @@ -370,6 +374,8 @@ const scoreArcMat = new THREE.MeshBasicMaterial({ }); const scoreArcMesh = new THREE.Mesh(buildScoreArcGeo(sovereigntyScore), scoreArcMat); scoreArcMesh.rotation.z = Math.PI / 2; // arc starts at 12 o'clock +scoreArcMesh.userData.inspectName = 'Sovereignty Meter'; +scoreArcMesh.userData.inspectDesc = 'Holographic arc gauge tracking Timmy\'s sovereignty score across all systems.'; sovereigntyGroup.add(scoreArcMesh); // Glow light at gauge center @@ -403,6 +409,8 @@ const meterSpriteMat = new THREE.SpriteMaterial({ }); const meterSprite = new THREE.Sprite(meterSpriteMat); meterSprite.scale.set(3.2, 1.6, 1); +meterSprite.userData.inspectName = 'Sovereignty Score'; +meterSprite.userData.inspectDesc = 'Live sovereignty score display. Reads from sovereignty-status.json.'; sovereigntyGroup.add(meterSprite); scene.add(sovereigntyGroup); @@ -749,6 +757,8 @@ async function initCommitBanners() { startDelay: i * 2.5, lifetime: 12 + i * 1.5, spawnTime: /** @type {number|null} */ (null), + inspectName: `Commit ${commit.hash}`, + inspectDesc: commit.message, }; scene.add(sprite); commitBanners.push(sprite); @@ -894,6 +904,8 @@ function rebuildAgentPanels(statusData) { baseY: BOARD_Y, floatPhase: (i / n) * Math.PI * 2, floatSpeed: 0.18 + i * 0.04, + inspectName: `Agent: ${agent.name.toUpperCase()}`, + inspectDesc: `Status: ${agent.status} · PRs today: ${agent.prs_today}${agent.issue ? '\n' + agent.issue : ''}`, }; agentBoardGroup.add(sprite); agentPanelSprites.push(sprite); @@ -922,3 +934,49 @@ async function refreshAgentBoard() { // Initial render, then poll every 30 s refreshAgentBoard(); setInterval(refreshAgentBoard, 30000); + +// === OBJECT INSPECTION (right-click) === +const inspectRaycaster = new THREE.Raycaster(); +const inspectTooltip = document.getElementById('inspect-tooltip'); +const inspectNameEl = document.getElementById('inspect-name'); +const inspectDescEl = document.getElementById('inspect-desc'); + +/** + * Shows the inspection tooltip near the cursor. + * @param {number} x - Client X position + * @param {number} y - Client Y position + * @param {string} name - Object name + * @param {string} desc - Object description + */ +function showInspectTooltip(x, y, name, desc) { + inspectNameEl.textContent = name; + inspectDescEl.textContent = desc; + // Offset from cursor; keep within viewport + const tx = Math.min(x + 14, window.innerWidth - 300); + const ty = Math.min(y + 14, window.innerHeight - 80); + inspectTooltip.style.left = tx + 'px'; + inspectTooltip.style.top = ty + 'px'; + inspectTooltip.classList.add('visible'); +} + +renderer.domElement.addEventListener('contextmenu', (/** @type {MouseEvent} */ e) => { + e.preventDefault(); + const mouse = new THREE.Vector2( + (e.clientX / window.innerWidth) * 2 - 1, + -(e.clientY / window.innerHeight) * 2 + 1 + ); + inspectRaycaster.setFromCamera(mouse, camera); + const hits = inspectRaycaster.intersectObjects(scene.children, true); + const hit = hits.find(h => h.object.userData.inspectName); + if (hit) { + const ud = hit.object.userData; + showInspectTooltip(e.clientX, e.clientY, ud.inspectName, ud.inspectDesc || ''); + } else { + inspectTooltip.classList.remove('visible'); + } +}); + +document.addEventListener('click', () => inspectTooltip.classList.remove('visible')); +document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') inspectTooltip.classList.remove('visible'); +}); diff --git a/index.html b/index.html index e5f82b4..9e11ad2 100644 --- a/index.html +++ b/index.html @@ -48,6 +48,11 @@
⚡ SOVEREIGNTY ⚡
+
+
+
+
+
diff --git a/style.css b/style.css index 8ccbc2d..8b742ec 100644 --- a/style.css +++ b/style.css @@ -184,6 +184,38 @@ body.photo-mode #overview-indicator { 100% { opacity: 0; transform: translate(-50%, -50%) scale(1); } } +/* === OBJECT INSPECTION TOOLTIP === */ +#inspect-tooltip { + display: none; + position: fixed; + z-index: 50; + background: rgba(0, 8, 24, 0.92); + border: 1px solid var(--color-primary); + padding: 8px 12px; + font-family: var(--font-body); + pointer-events: none; + max-width: 280px; +} + +#inspect-tooltip.visible { + display: block; +} + +#inspect-name { + font-size: 11px; + color: var(--color-primary); + letter-spacing: 0.15em; + text-transform: uppercase; + margin-bottom: 4px; +} + +#inspect-desc { + font-size: 11px; + color: var(--color-text); + line-height: 1.4; + opacity: 0.85; +} + /* === CRT / CYBERPUNK OVERLAY === */ .crt-overlay { position: fixed;