| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Exploratory Atlas | 3D Knowledge Navigator</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script> |
| <style> |
| body { |
| margin: 0; |
| overflow: hidden; |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| } |
| #container { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| } |
| #ui { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| } |
| #ui > * { |
| pointer-events: auto; |
| } |
| .panel { |
| background: rgba(10, 20, 30, 0.85); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(100, 180, 255, 0.2); |
| border-radius: 12px; |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); |
| color: white; |
| } |
| .holographic { |
| position: relative; |
| overflow: hidden; |
| } |
| .holographic::before { |
| content: ''; |
| position: absolute; |
| top: -50%; |
| left: -50%; |
| width: 200%; |
| height: 200%; |
| background: linear-gradient( |
| to bottom right, |
| rgba(100, 200, 255, 0.1), |
| rgba(100, 200, 255, 0.05), |
| rgba(100, 200, 255, 0.1) |
| ); |
| transform: rotate(30deg); |
| z-index: -1; |
| } |
| .glow { |
| text-shadow: 0 0 10px rgba(100, 200, 255, 0.7); |
| } |
| .node-info { |
| transition: all 0.3s ease; |
| max-height: 0; |
| opacity: 0; |
| overflow: hidden; |
| } |
| .node-info.active { |
| max-height: 500px; |
| opacity: 1; |
| } |
| .connection-line { |
| stroke-dasharray: 1000; |
| stroke-dashoffset: 1000; |
| animation: dash 5s linear forwards; |
| } |
| @keyframes dash { |
| to { |
| stroke-dashoffset: 0; |
| } |
| } |
| .pulse { |
| animation: pulse 2s infinite; |
| } |
| @keyframes pulse { |
| 0%, 100% { opacity: 0.8; } |
| 50% { opacity: 1; } |
| } |
| </style> |
| </head> |
| <body> |
| <div id="container"></div> |
| |
| <div id="ui"> |
| |
| <div class="absolute top-4 left-4 right-4 flex justify-between items-start"> |
| <div class="panel p-4 w-1/3"> |
| <h1 class="text-2xl font-bold glow">EXPLORATORY ATLAS</h1> |
| <p class="text-blue-300">3D Knowledge Navigation System</p> |
| </div> |
| |
| <div class="panel p-4 flex space-x-4"> |
| <div> |
| <label class="block text-blue-300 text-sm mb-1">FROM</label> |
| <input type="text" id="topicX" value="Artificial Intelligence" |
| class="bg-transparent border-b border-blue-500 focus:outline-none text-white"> |
| </div> |
| <div> |
| <label class="block text-blue-300 text-sm mb-1">TO</label> |
| <input type="text" id="topicY" value="Climate Change" |
| class="bg-transparent border-b border-blue-500 focus:outline-none text-white"> |
| </div> |
| <button id="generateBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-1 rounded-lg flex items-center"> |
| <i class="fas fa-satellite-dish mr-2"></i> Generate |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="panel absolute bottom-4 left-4 p-4 w-80"> |
| <h2 class="text-lg font-semibold mb-3 flex items-center"> |
| <i class="fas fa-cogs mr-2 text-blue-400"></i> Navigation Controls |
| </h2> |
| |
| <div class="grid grid-cols-2 gap-3 mb-4"> |
| <button class="bg-blue-800 hover:bg-blue-700 p-2 rounded-lg flex flex-col items-center"> |
| <i class="fas fa-flag mb-1"></i> |
| <span class="text-xs">Place Marker</span> |
| </button> |
| <button class="bg-purple-800 hover:bg-purple-700 p-2 rounded-lg flex flex-col items-center"> |
| <i class="fas fa-digging mb-1"></i> |
| <span class="text-xs">Excavate</span> |
| </button> |
| <button class="bg-green-800 hover:bg-green-700 p-2 rounded-lg flex flex-col items-center"> |
| <i class="fas fa-expand mb-1"></i> |
| <span class="text-xs">Focus Node</span> |
| </button> |
| <button class="bg-yellow-800 hover:bg-yellow-700 p-2 rounded-lg flex flex-col items-center"> |
| <i class="fas fa-route mb-1"></i> |
| <span class="text-xs">Trace Path</span> |
| </button> |
| </div> |
| |
| <div class="mb-3"> |
| <h3 class="text-sm font-semibold mb-2 flex items-center"> |
| <i class="fas fa-map-marked-alt mr-1 text-blue-400"></i> Legend |
| </h3> |
| <div class="grid grid-cols-2 gap-2 text-xs"> |
| <div class="flex items-center"> |
| <div class="w-3 h-3 bg-green-500 rounded-full mr-2"></div> |
| <span>Core Concepts</span> |
| </div> |
| <div class="flex items-center"> |
| <div class="w-3 h-3 bg-red-500 rounded-full mr-2"></div> |
| <span>Contradictions</span> |
| </div> |
| <div class="flex items-center"> |
| <div class="w-3 h-3 bg-blue-500 rounded-full mr-2"></div> |
| <span>Relationships</span> |
| </div> |
| <div class="flex items-center"> |
| <div class="w-3 h-3 bg-gray-400 rounded-full mr-2 pulse"></div> |
| <span>Uncertainty</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="panel absolute top-1/2 right-4 transform -translate-y-1/2 w-80 max-h-[70vh] overflow-y-auto"> |
| <div class="p-4"> |
| <h2 id="nodeTitle" class="text-xl font-bold mb-2">Knowledge Node</h2> |
| <div id="nodeType" class="text-xs px-2 py-1 bg-blue-900 rounded-full inline-block mb-3">Concept</div> |
| <div id="nodeContent" class="text-sm"> |
| <p>Select a node in the visualization to view detailed information.</p> |
| </div> |
| |
| <div id="nodeConnections" class="mt-4"> |
| <h3 class="text-sm font-semibold mb-2">Connected Concepts</h3> |
| <div class="space-y-2"> |
| |
| </div> |
| </div> |
| |
| <div class="mt-6 pt-4 border-t border-gray-700"> |
| <button class="bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded text-sm mr-2"> |
| <i class="fas fa-bookmark mr-1"></i> Save |
| </button> |
| <button class="bg-green-600 hover:bg-green-700 px-3 py-1 rounded text-sm"> |
| <i class="fas fa-share-alt mr-1"></i> Share |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="panel absolute bottom-4 right-4 w-1/3"> |
| <div class="p-4"> |
| <h2 class="text-lg font-semibold mb-3 flex items-center"> |
| <i class="fas fa-history mr-2 text-blue-400"></i> Exploration Path |
| </h2> |
| <div class="flex overflow-x-auto space-x-2 py-2"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let scene, camera, renderer, controls; |
| let knowledgeNodes = []; |
| let connections = []; |
| let selectedNode = null; |
| |
| initThreeJS(); |
| createDemoKnowledgeGraph(); |
| animate(); |
| |
| function initThreeJS() { |
| |
| scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x020a13); |
| scene.fog = new THREE.FogExp2(0x020a13, 0.002); |
| |
| |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| camera.position.z = 50; |
| camera.position.y = 30; |
| |
| |
| renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setPixelRatio(window.devicePixelRatio); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| renderer.shadowMap.enabled = true; |
| document.getElementById('container').appendChild(renderer.domElement); |
| |
| |
| controls = new THREE.OrbitControls(camera, renderer.domElement); |
| controls.enableDamping = true; |
| controls.dampingFactor = 0.05; |
| controls.minDistance = 20; |
| controls.maxDistance = 200; |
| |
| |
| const ambientLight = new THREE.AmbientLight(0x404040); |
| scene.add(ambientLight); |
| |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 1); |
| directionalLight.position.set(1, 1, 1); |
| directionalLight.castShadow = true; |
| scene.add(directionalLight); |
| |
| const hemisphereLight = new THREE.HemisphereLight(0x00aaff, 0xffaa00, 0.3); |
| scene.add(hemisphereLight); |
| |
| |
| const starsGeometry = new THREE.BufferGeometry(); |
| const starVertices = []; |
| for (let i = 0; i < 10000; i++) { |
| const x = (Math.random() - 0.5) * 2000; |
| const y = (Math.random() - 0.5) * 2000; |
| const z = (Math.random() - 0.5) * 2000; |
| starVertices.push(x, y, z); |
| } |
| starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3)); |
| const starsMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.1 }); |
| const starField = new THREE.Points(starsGeometry, starsMaterial); |
| scene.add(starField); |
| |
| |
| window.addEventListener('resize', () => { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }); |
| } |
| |
| function createDemoKnowledgeGraph() { |
| |
| knowledgeNodes.forEach(node => scene.remove(node.mesh)); |
| connections.forEach(conn => scene.remove(conn.line)); |
| knowledgeNodes = []; |
| connections = []; |
| |
| |
| createNode("AI & Climate", 0, 0, 0, "core", 5); |
| |
| |
| const node1 = createNode("Ethical AI", -20, 5, 15, "core", 4); |
| const node2 = createNode("Carbon Footprint", 15, 8, -10, "core", 4); |
| const node3 = createNode("Data Centers", 5, -10, -15, "relationship", 3.5); |
| const node4 = createNode("AI Solutions", -10, -15, 20, "relationship", 3.5); |
| const node5 = createNode("Policy Impact", 25, -5, 5, "contradiction", 3); |
| const node6 = createNode("Future Predictions", -5, 25, -5, "uncertainty", 3); |
| |
| |
| createConnection(knowledgeNodes[0], node1, "strong"); |
| createConnection(knowledgeNodes[0], node2, "strong"); |
| createConnection(knowledgeNodes[0], node3, "medium"); |
| createConnection(knowledgeNodes[0], node4, "medium"); |
| createConnection(node1, node4, "weak"); |
| createConnection(node2, node3, "strong"); |
| createConnection(node2, node5, "weak"); |
| createConnection(node3, node6, "weak"); |
| createConnection(node4, node6, "medium"); |
| |
| |
| addTechElements(); |
| } |
| |
| function createNode(title, x, y, z, type, size) { |
| let geometry, material; |
| const color = getColorForType(type); |
| |
| if (type === "core") { |
| geometry = new THREE.IcosahedronGeometry(size, 1); |
| material = new THREE.MeshPhongMaterial({ |
| color: color, |
| emissive: color, |
| emissiveIntensity: 0.2, |
| shininess: 100, |
| wireframe: false |
| }); |
| } else if (type === "relationship") { |
| geometry = new THREE.TorusGeometry(size * 0.7, size * 0.3, 16, 32); |
| material = new THREE.MeshPhongMaterial({ |
| color: color, |
| emissive: color, |
| emissiveIntensity: 0.1, |
| shininess: 50, |
| wireframe: false |
| }); |
| } else if (type === "contradiction") { |
| geometry = new THREE.OctahedronGeometry(size, 0); |
| material = new THREE.MeshPhongMaterial({ |
| color: color, |
| emissive: color, |
| emissiveIntensity: 0.3, |
| shininess: 30, |
| wireframe: false |
| }); |
| } else { |
| geometry = new THREE.SphereGeometry(size, 32, 32); |
| material = new THREE.MeshPhongMaterial({ |
| color: color, |
| emissive: color, |
| emissiveIntensity: 0.05, |
| transparent: true, |
| opacity: 0.7, |
| wireframe: true |
| }); |
| } |
| |
| const mesh = new THREE.Mesh(geometry, material); |
| mesh.position.set(x, y, z); |
| mesh.castShadow = true; |
| mesh.receiveShadow = true; |
| |
| |
| if (type === "uncertainty") { |
| mesh.userData.pulseSpeed = 0.02 + Math.random() * 0.03; |
| mesh.userData.originalScale = size; |
| } |
| |
| |
| mesh.userData = { title, type, description: generateDescription(title, type) }; |
| mesh.userData.connections = []; |
| |
| scene.add(mesh); |
| |
| |
| const label = document.createElement('div'); |
| label.className = `absolute text-white text-xs font-medium bg-black bg-opacity-50 px-2 py-1 rounded pointer-events-none`; |
| label.textContent = title; |
| document.getElementById('ui').appendChild(label); |
| |
| mesh.userData.label = label; |
| |
| knowledgeNodes.push({ mesh, label, title, type }); |
| return { mesh, label, title, type }; |
| } |
| |
| function createConnection(node1, node2, strength) { |
| const start = node1.mesh.position; |
| const end = node2.mesh.position; |
| |
| |
| const curve = new THREE.CatmullRomCurve3([ |
| new THREE.Vector3(start.x, start.y, start.z), |
| new THREE.Vector3( |
| (start.x + end.x) / 2 + (Math.random() - 0.5) * 10, |
| (start.y + end.y) / 2 + (Math.random() - 0.5) * 10, |
| (start.z + end.z) / 2 + (Math.random() - 0.5) * 10 |
| ), |
| new THREE.Vector3(end.x, end.y, end.z) |
| ]); |
| |
| const points = curve.getPoints(50); |
| const geometry = new THREE.BufferGeometry().setFromPoints(points); |
| |
| let color, linewidth; |
| if (strength === "strong") { |
| color = 0x00ff00; |
| linewidth = 2; |
| } else if (strength === "medium") { |
| color = 0x0088ff; |
| linewidth = 1.5; |
| } else { |
| color = 0x888888; |
| linewidth = 1; |
| } |
| |
| const material = new THREE.LineBasicMaterial({ |
| color: color, |
| linewidth: linewidth, |
| transparent: true, |
| opacity: 0.7 |
| }); |
| |
| const line = new THREE.Line(geometry, material); |
| scene.add(line); |
| |
| |
| const connection = { |
| node1, node2, strength, line, |
| description: `${node1.title} ↔ ${node2.title} (${strength})` |
| }; |
| |
| connections.push(connection); |
| node1.mesh.userData.connections.push(connection); |
| node2.mesh.userData.connections.push(connection); |
| |
| return connection; |
| } |
| |
| function addTechElements() { |
| |
| const techGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.2, 6); |
| const techMaterial = new THREE.MeshPhongMaterial({ |
| color: 0x00aaff, |
| emissive: 0x0044ff, |
| emissiveIntensity: 0.5, |
| transparent: true, |
| opacity: 0.8 |
| }); |
| |
| for (let i = 0; i < 20; i++) { |
| const tech = new THREE.Mesh(techGeometry, techMaterial); |
| tech.position.set( |
| (Math.random() - 0.5) * 100, |
| (Math.random() - 0.5) * 100, |
| (Math.random() - 0.5) * 100 |
| ); |
| tech.rotation.x = Math.random() * Math.PI; |
| tech.rotation.y = Math.random() * Math.PI; |
| tech.userData = { speed: 0.01 + Math.random() * 0.02 }; |
| scene.add(tech); |
| } |
| } |
| |
| function getColorForType(type) { |
| switch(type) { |
| case "core": return 0x00aa00; |
| case "relationship": return 0x0066ff; |
| case "contradiction": return 0xff3300; |
| case "uncertainty": return 0x888888; |
| default: return 0xffffff; |
| } |
| } |
| |
| function generateDescription(title, type) { |
| const descriptors = { |
| core: ["Fundamental concept", "Key principle", "Central idea"], |
| relationship: ["Connected to", "Influences", "Related to"], |
| contradiction: ["Contrasts with", "Opposes", "Conflicts with"], |
| uncertainty: ["Emerging concept", "Debated topic", "Unclear relationship"] |
| }; |
| |
| const aspects = { |
| "AI & Climate": "the intersection of artificial intelligence and climate science", |
| "Ethical AI": "ethical considerations in AI development", |
| "Carbon Footprint": "environmental impact of technology", |
| "Data Centers": "energy consumption of computing infrastructure", |
| "AI Solutions": "how AI can help address climate change", |
| "Policy Impact": "how policies affect AI and climate initiatives", |
| "Future Predictions": "speculative outcomes of AI on climate" |
| }; |
| |
| const descType = descriptors[type][Math.floor(Math.random() * descriptors[type].length)]; |
| const aspect = aspects[title] || "this concept"; |
| |
| return `${descType} about ${aspect}. This ${type} node represents important knowledge in the network.`; |
| } |
| |
| function updateNodeInfo(node) { |
| document.getElementById('nodeTitle').textContent = node.title; |
| document.getElementById('nodeType').textContent = node.type.charAt(0).toUpperCase() + node.type.slice(1); |
| document.getElementById('nodeType').className = `text-xs px-2 py-1 rounded-full inline-block mb-3 ${ |
| node.type === 'core' ? 'bg-green-900' : |
| node.type === 'relationship' ? 'bg-blue-900' : |
| node.type === 'contradiction' ? 'bg-red-900' : 'bg-gray-700' |
| }`; |
| |
| document.getElementById('nodeContent').innerHTML = ` |
| <p class="mb-3">${node.mesh.userData.description}</p> |
| <div class="bg-gray-800 p-3 rounded-lg text-xs"> |
| <p class="font-semibold mb-1">Key Attributes:</p> |
| <ul class="list-disc pl-4"> |
| <li>${node.type === 'core' ? 'Well-established knowledge' : |
| node.type === 'relationship' ? 'Demonstrated connection' : |
| node.type === 'contradiction' ? 'Opposing evidence exists' : 'Emerging/uncertain evidence'}</li> |
| <li>Connected to ${node.mesh.userData.connections.length} other nodes</li> |
| <li>${node.type === 'uncertainty' ? 'Requires further research' : 'Substantial evidence available'}</li> |
| </ul> |
| </div> |
| `; |
| |
| |
| const connectionsContainer = document.getElementById('nodeConnections'); |
| connectionsContainer.innerHTML = ` |
| <h3 class="text-sm font-semibold mb-2">Connected Concepts</h3> |
| <div class="space-y-2"> |
| ${node.mesh.userData.connections.map(conn => { |
| const otherNode = conn.node1 === node ? conn.node2 : conn.node1; |
| return ` |
| <div class="flex items-center p-2 bg-gray-800 rounded-lg cursor-pointer hover:bg-gray-700" |
| onclick="focusNode('${otherNode.title}')"> |
| <div class="w-3 h-3 rounded-full mr-2 ${ |
| otherNode.type === 'core' ? 'bg-green-500' : |
| otherNode.type === 'relationship' ? 'bg-blue-500' : |
| otherNode.type === 'contradiction' ? 'bg-red-500' : 'bg-gray-400' |
| }"></div> |
| <span class="text-sm">${otherNode.title}</span> |
| <span class="ml-auto text-xs opacity-70">${conn.strength}</span> |
| </div> |
| `; |
| }).join('')} |
| </div> |
| `; |
| } |
| |
| function focusNode(title) { |
| const node = knowledgeNodes.find(n => n.title === title); |
| if (node) { |
| |
| const targetPosition = new THREE.Vector3(); |
| node.mesh.getWorldPosition(targetPosition); |
| |
| |
| const direction = new THREE.Vector3().subVectors(camera.position, targetPosition).normalize(); |
| const focusPosition = new THREE.Vector3().copy(targetPosition).add(direction.multiplyScalar(15)); |
| |
| |
| focusPosition.y += 5; |
| |
| |
| animateCameraToPosition(focusPosition, targetPosition); |
| |
| |
| highlightNode(node); |
| } |
| } |
| |
| function animateCameraToPosition(newPosition, lookAt) { |
| const startPosition = camera.position.clone(); |
| const startQuaternion = camera.quaternion.clone(); |
| |
| camera.lookAt(lookAt); |
| const endQuaternion = camera.quaternion.clone(); |
| camera.quaternion.copy(startQuaternion); |
| |
| let progress = 0; |
| const duration = 1000; |
| const startTime = Date.now(); |
| |
| function updateCamera() { |
| progress = (Date.now() - startTime) / duration; |
| if (progress >= 1) { |
| camera.position.copy(newPosition); |
| camera.quaternion.copy(endQuaternion); |
| return; |
| } |
| |
| |
| progress = progress < 0.5 ? |
| 2 * progress * progress : |
| 1 - Math.pow(-2 * progress + 2, 2) / 2; |
| |
| camera.position.lerpVectors(startPosition, newPosition, progress); |
| |
| |
| THREE.Quaternion.slerp(startQuaternion, endQuaternion, camera.quaternion, progress); |
| |
| requestAnimationFrame(updateCamera); |
| } |
| |
| updateCamera(); |
| } |
| |
| function highlightNode(node) { |
| |
| knowledgeNodes.forEach(n => { |
| n.mesh.material.emissiveIntensity = n.type === 'uncertainty' ? 0.05 : 0.2; |
| n.mesh.scale.set(1, 1, 1); |
| }); |
| |
| |
| node.mesh.material.emissiveIntensity = 0.8; |
| node.mesh.scale.set(1.2, 1.2, 1.2); |
| |
| |
| updateNodeInfo(node); |
| selectedNode = node; |
| |
| |
| addToExplorationPath(node); |
| } |
| |
| function addToExplorationPath(node) { |
| const pathContainer = document.querySelector('#ui > .panel.absolute.bottom-4.right-4 .flex'); |
| |
| const pathItem = document.createElement('div'); |
| pathItem.className = `flex-shrink-0 px-3 py-1 rounded-full text-sm flex items-center ${ |
| node.type === 'core' ? 'bg-green-900 text-green-300' : |
| node.type === 'relationship' ? 'bg-blue-900 text-blue-300' : |
| node.type === 'contradiction' ? 'bg-red-900 text-red-300' : 'bg-gray-800 text-gray-300' |
| }`; |
| |
| pathItem.innerHTML = ` |
| <i class="fas ${ |
| node.type === 'core' ? 'fa-certificate' : |
| node.type === 'relationship' ? 'fa-link' : |
| node.type === 'contradiction' ? 'fa-exclamation-triangle' : 'fa-question-circle' |
| } mr-1"></i> ${node.title} |
| `; |
| |
| pathContainer.appendChild(pathItem); |
| pathContainer.scrollLeft = pathContainer.scrollWidth; |
| } |
| |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| |
| |
| controls.update(); |
| |
| |
| knowledgeNodes.forEach(node => { |
| const vector = node.mesh.position.clone().project(camera); |
| const x = (vector.x * 0.5 + 0.5) * window.innerWidth; |
| const y = (-(vector.y * 0.5) + 0.5) * window.innerHeight; |
| |
| node.label.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`; |
| node.label.style.zIndex = vector.z > 1 ? -1 : Math.round((1 - vector.z) * 10000); |
| }); |
| |
| |
| knowledgeNodes.filter(n => n.type === 'uncertainty').forEach(node => { |
| const scale = node.mesh.userData.originalScale * (1 + Math.sin(Date.now() * node.mesh.userData.pulseSpeed) * 0.1); |
| node.mesh.scale.set(scale, scale, scale); |
| }); |
| |
| |
| scene.children.filter(obj => obj.userData?.speed).forEach(obj => { |
| obj.rotation.x += obj.userData.speed * 0.5; |
| obj.rotation.y += obj.userData.speed; |
| obj.position.y += Math.sin(Date.now() * 0.001 + obj.position.x) * 0.02; |
| }); |
| |
| renderer.render(scene, camera); |
| } |
| |
| |
| const raycaster = new THREE.Raycaster(); |
| const mouse = new THREE.Vector2(); |
| |
| function onMouseClick(event) { |
| |
| mouse.x = (event.clientX / window.innerWidth) * 2 - 1; |
| mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; |
| |
| |
| raycaster.setFromCamera(mouse, camera); |
| |
| |
| const intersects = raycaster.intersectObjects( |
| knowledgeNodes.map(n => n.mesh) |
| ); |
| |
| if (intersects.length > 0) { |
| const clickedNode = knowledgeNodes.find(n => n.mesh === intersects[0].object); |
| if (clickedNode) { |
| highlightNode(clickedNode); |
| } |
| } |
| } |
| |
| window.addEventListener('click', onMouseClick, false); |
| |
| |
| document.getElementById('generateBtn').addEventListener('click', function() { |
| const topicX = document.getElementById('topicX').value || 'Topic X'; |
| const topicY = document.getElementById('topicY').value || 'Topic Y'; |
| |
| |
| document.querySelector('header h1').textContent = `EXPLORATORY ATLAS: ${topicX} ↔ ${topicY}`; |
| |
| |
| |
| createDemoKnowledgeGraph(); |
| |
| |
| const notification = document.createElement('div'); |
| notification.className = 'fixed top-4 right-4 bg-blue-600 text-white px-4 py-2 rounded-lg shadow-lg transform translate-x-0 transition-transform'; |
| notification.innerHTML = `<i class="fas fa-satellite-dish mr-2"></i> Generating knowledge map for ${topicX} and ${topicY}`; |
| document.body.appendChild(notification); |
| |
| setTimeout(() => { |
| notification.style.transform = 'translateX(200%)'; |
| setTimeout(() => notification.remove(), 300); |
| }, 3000); |
| }); |
| |
| |
| window.focusNode = focusNode; |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LukasBe/3d-knowledge-navigation-system" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |