diff --git a/app.js b/app.js index 6e85528..f038dc7 100644 --- a/app.js +++ b/app.js @@ -146,11 +146,108 @@ function animate() { // Subtle pulse on constellation opacity constellationLines.material.opacity = 0.12 + Math.sin(elapsed * 0.5) * 0.06; + // Animate beacons + for (const beacon of beacons) { + const age = elapsed - beacon.userData.createdAt; + const pulse = 0.5 + Math.sin(age * 2.5) * 0.5; // 0..1 + const glow = beacon.children[1]; + if (glow) { + glow.material.opacity = 0.05 + pulse * 0.18; + glow.scale.setScalar(1 + pulse * 0.4); + } + const ring = beacon.children[2]; + if (ring) { + ring.rotation.y += 0.025; + ring.material.opacity = 0.25 + pulse * 0.45; + } + } + renderer.render(scene, camera); } animate(); +// === BEACON SYSTEM === +const beacons = []; +const BEACON_COLOR = 0x00ffcc; +const MAX_BEACONS = 10; + +function createBeacon(position) { + const group = new THREE.Group(); + + // Core sphere + const coreGeo = new THREE.SphereGeometry(0.12, 8, 8); + const coreMat = new THREE.MeshBasicMaterial({ color: BEACON_COLOR }); + const core = new THREE.Mesh(coreGeo, coreMat); + group.add(core); // index 0 + + // Outer glow sphere + const glowGeo = new THREE.SphereGeometry(0.35, 8, 8); + const glowMat = new THREE.MeshBasicMaterial({ + color: BEACON_COLOR, + transparent: true, + opacity: 0.15, + }); + const glow = new THREE.Mesh(glowGeo, glowMat); + group.add(glow); // index 1 + + // Orbiting ring + const ringGeo = new THREE.RingGeometry(0.22, 0.28, 20); + const ringMat = new THREE.MeshBasicMaterial({ + color: BEACON_COLOR, + transparent: true, + opacity: 0.6, + side: THREE.DoubleSide, + }); + const ring = new THREE.Mesh(ringGeo, ringMat); + ring.rotation.x = Math.PI / 2; + group.add(ring); // index 2 + + // Point light for environment glow + const light = new THREE.PointLight(BEACON_COLOR, 2.0, 6); + group.add(light); + + group.position.copy(position); + group.userData.createdAt = clock.getElapsedTime(); + + scene.add(group); + beacons.push(group); + + // Remove oldest beacon when over limit + if (beacons.length > MAX_BEACONS) { + const oldest = beacons.shift(); + scene.remove(oldest); + } + + updateBeaconHUD(); +} + +function updateBeaconHUD() { + const el = document.getElementById('beacon-count'); + if (el) el.textContent = beacons.length > 0 ? `⬡ ${beacons.length}` : ''; +} + +// Click to place beacon +renderer.domElement.addEventListener('click', (e) => { + const ndc = new THREE.Vector3( + (e.clientX / window.innerWidth) * 2 - 1, + -(e.clientY / window.innerHeight) * 2 + 1, + 0.5 + ); + ndc.unproject(camera); + const dir = ndc.sub(camera.position).normalize(); + const pos = camera.position.clone().add(dir.multiplyScalar(15)); + createBeacon(pos); +}); + +// Right-click to clear all beacons +renderer.domElement.addEventListener('contextmenu', (e) => { + e.preventDefault(); + beacons.forEach(b => scene.remove(b)); + beacons.length = 0; + updateBeaconHUD(); +}); + // === DEBUG MODE === let debugMode = false; diff --git a/index.html b/index.html index 2006413..e81b1e8 100644 --- a/index.html +++ b/index.html @@ -36,6 +36,14 @@ + +