Files
JARVIS/frontend/prototypes/holographic-brain-demo.html

832 lines
38 KiB
HTML

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>J.A.R.V.I.S. - 知识脑图</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
:root {
--jarvis-blue: #00f3ff;
--jarvis-blue-dim: rgba(0, 243, 255, 0.1);
--jarvis-alert: #ff3333;
--bg-color: #020408;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background-color: var(--bg-color);
color: var(--jarvis-blue);
font-family: 'Orbitron', sans-serif;
overflow: hidden;
margin: 0;
height: 100vh;
width: 100vw;
}
body::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0) 50%,
rgba(0, 0, 0, 0.1) 50%,
rgba(0, 0, 0, 0.1));
background-size: 100% 3px;
pointer-events: none;
z-index: 50;
}
.vignette {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle, transparent 50%, rgba(0, 0, 0, 0.95) 100%);
pointer-events: none;
z-index: 40;
}
.text-glow {
text-shadow: 0 0 5px var(--jarvis-blue), 0 0 10px var(--jarvis-blue);
}
.tech-panel {
background: rgba(0, 15, 30, 0.7);
border-left: 1px solid rgba(0, 243, 255, 0.3);
border-right: 1px solid rgba(0, 243, 255, 0.3);
position: relative;
backdrop-filter: blur(4px);
transition: all 0.3s ease;
}
.tech-panel:hover {
border-color: rgba(0, 243, 255, 0.8);
background: rgba(0, 20, 40, 0.8);
}
.tech-panel::before,
.tech-panel::after {
content: '';
position: absolute;
width: 8px;
height: 8px;
border: 1px solid var(--jarvis-blue);
transition: all 0.3s;
}
.tech-panel::before {
top: -1px;
left: -1px;
border-right: none;
border-bottom: none;
}
.tech-panel::after {
bottom: -1px;
right: -1px;
border-left: none;
border-top: none;
}
.tech-border {
clip-path: polygon(10px 0,
100% 0,
100% calc(100% - 10px),
calc(100% - 10px) 100%,
0 100%,
0 10px);
border-top: 1px solid var(--jarvis-blue-dim);
}
@keyframes pulse-status {
0%, 100% { opacity: 0.6; text-shadow: 0 0 2px var(--jarvis-blue); box-shadow: 0 0 5px var(--jarvis-blue-dim); }
50% { opacity: 1; text-shadow: 0 0 15px var(--jarvis-blue); box-shadow: 0 0 15px var(--jarvis-blue-dim); }
}
.status-pulse {
animation: pulse-status 3s infinite ease-in-out;
}
@keyframes flash-row {
0% { background-color: transparent; }
50% { background-color: rgba(0, 243, 255, 0.15); }
100% { background-color: transparent; }
}
.flash-row:hover {
animation: flash-row 0.8s infinite;
cursor: pointer;
background: rgba(0, 243, 255, 0.05);
}
#brain-canvas-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
}
#brain-canvas-container canvas {
pointer-events: auto;
cursor: grab;
}
#brain-canvas-container canvas:active {
cursor: grabbing;
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
box-shadow: 0 0 6px currentColor;
}
.detail-panel {
transform: translateX(100%);
transition: transform 0.3s ease;
}
.detail-panel.active {
transform: translateX(0);
}
</style>
</head>
<body>
<div class="vignette"></div>
<div class="relative z-30 h-full w-full flex flex-col p-4 md:p-6 pointer-events-none">
<header class="flex justify-between items-start pointer-events-auto mb-2 border-b border-cyan-500/20 pb-2">
<div class="flex items-center gap-4">
<div class="flex flex-col items-center justify-center">
<div class="w-2 h-2 bg-cyan-400 rounded-full mb-1"></div>
<div class="h-8 w-px bg-cyan-500/50"></div>
</div>
<div>
<h1 class="text-4xl font-bold tracking-[0.15em] text-glow font-orbitron">J.A.R.V.I.S.</h1>
<div class="flex items-center gap-2 mt-1">
<div class="h-1 w-20 bg-cyan-500/30 overflow-hidden">
<div class="h-full bg-cyan-400 w-1/2 animate-pulse"></div>
</div>
<div class="text-[9px] tracking-[0.3em] opacity-70">KNOWLEDGE BRAIN V2.0</div>
</div>
</div>
</div>
<div class="text-right">
<div class="flex items-center justify-end gap-3 mb-1">
<span class="text-[10px] tracking-widest opacity-60">BRAIN STATUS</span>
<span id="brain-status" class="font-bold text-xs status-pulse bg-cyan-900/40 px-3 py-1 rounded-sm border border-cyan-500/30">INITIALIZING</span>
</div>
<div class="flex justify-end items-baseline gap-2">
<span class="text-[10px] opacity-50">SYS_TIME</span>
<div id="time-display" class="font-mono text-xl tracking-wider text-cyan-200">00:00:00</div>
</div>
</div>
</header>
<div class="flex-grow grid grid-cols-1 md:grid-cols-4 gap-6 pointer-events-auto h-0 min-h-0 mt-2">
<div class="flex flex-col gap-4 h-full overflow-hidden">
<div class="tech-panel p-4 tech-border">
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
NODE REGISTRY</h3>
<div class="space-y-2 font-mono text-[10px] max-h-48 overflow-y-auto pr-1">
<div id="node-list"></div>
</div>
</div>
<div class="tech-panel p-4 tech-border">
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
BRAIN METRICS</h3>
<div class="space-y-4 font-mono text-[10px]">
<div>
<div class="flex justify-between mb-1"><span class="opacity-70">NODE_COUNT</span><span class="text-cyan-300" id="metric-nodes">0</span></div>
<div class="w-full bg-cyan-900/50 h-1">
<div id="nodes-bar" class="sys-bar bg-cyan-400 h-full w-[0%] shadow-[0_0_8px_#00f3ff]"></div>
</div>
</div>
<div>
<div class="flex justify-between mb-1"><span class="opacity-70">LINK_DENSITY</span><span class="text-cyan-300" id="metric-links">0</span></div>
<div class="w-full bg-cyan-900/50 h-1">
<div id="links-bar" class="sys-bar bg-cyan-400 h-full w-[0%] shadow-[0_0_8px_#00f3ff]"></div>
</div>
</div>
<div>
<div class="flex justify-between mb-1"><span class="opacity-70">CLUSTER_COUNT</span><span class="text-cyan-300" id="metric-clusters">0</span></div>
<div class="w-full bg-cyan-900/50 h-1">
<div id="clusters-bar" class="sys-bar bg-cyan-400 h-full w-[0%] shadow-[0_0_8px_#00f3ff]"></div>
</div>
</div>
<div class="grid grid-cols-2 gap-2 pt-2">
<div class="bg-cyan-900/20 p-2 border border-cyan-500/20 text-center">
<div class="opacity-50 text-[9px]">CORE_NODES</div>
<div id="core-nodes" class="text-sm font-bold text-cyan-300">0</div>
</div>
<div class="bg-cyan-900/20 p-2 border border-cyan-500/20 text-center">
<div class="opacity-50 text-[9px]">BRIDGE_NODES</div>
<div id="bridge-nodes" class="text-sm font-bold text-cyan-300">0</div>
</div>
</div>
</div>
</div>
<div class="tech-panel p-4 flex-grow overflow-hidden flex flex-col">
<div class="flex justify-between items-center border-b border-cyan-500/30 pb-2 mb-2">
<h3 class="text-xs font-bold tracking-widest text-cyan-300">SIGNAL LEGEND</h3>
</div>
<div class="space-y-3 font-mono text-[11px]">
<div class="flex items-center gap-2">
<div class="legend-dot" style="background: #00f3ff; color: #00f3ff;"></div>
<span class="text-cyan-300">KNOWLEDGE</span>
</div>
<div class="flex items-center gap-2">
<div class="legend-dot" style="background: #ff6b9d; color: #ff6b9d;"></div>
<span class="text-cyan-300">CHAT</span>
</div>
<div class="flex items-center gap-2">
<div class="legend-dot" style="background: #a855f7; color: #a855f7;"></div>
<span class="text-cyan-300">FORUM</span>
</div>
<div class="flex items-center gap-2">
<div class="legend-dot" style="background: #fbbf24; color: #fbbf24;"></div>
<span class="text-cyan-300">SCHEDULE</span>
</div>
</div>
</div>
</div>
<div class="md:col-span-2 relative flex flex-col justify-end items-center">
<div class="absolute top-10 w-full text-center pointer-events-none">
<div id="brain-title" class="text-xl md:text-2xl font-bold tracking-[0.3em] text-glow uppercase text-cyan-100">
NEURAL BRAIN MATRIX
</div>
<div class="text-[10px] text-cyan-400/70 tracking-[0.5em] mt-2">INTERACTIVE 3D GRAPH</div>
<div class="text-[10px] text-cyan-400/50 tracking-[0.2em] mt-1">DRAG TO ROTATE / SCROLL TO ZOOM</div>
</div>
<div id="brain-canvas-container" class="w-full h-full"></div>
<div class="terminal-window w-full h-48 bg-black/90 border-t border-cyan-500/50 rounded-tl-lg rounded-tr-lg overflow-hidden flex flex-col backdrop-blur-md shadow-[0_-5px_20px_rgba(0,243,255,0.1)]">
<div class="bg-cyan-900/20 px-3 py-1.5 text-[10px] flex justify-between border-b border-cyan-500/30 items-center">
<span class="tracking-widest text-cyan-400">BRAIN_CONSOLE // SYSTEM LOG</span>
<div class="flex gap-2 font-mono text-cyan-600">
<span id="active-nodes-val">ACTIVE: 0</span>
<span id="selected-val">SELECTED: NONE</span>
</div>
</div>
<div id="brain-terminal" class="p-4 font-mono text-xs text-cyan-300/80 overflow-y-auto flex-grow space-y-1.5">
</div>
</div>
</div>
<div class="flex flex-col gap-4 h-full overflow-hidden">
<div id="detail-panel" class="tech-panel p-4 tech-border detail-panel">
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
SELECTED NODE</h3>
<div id="node-detail" class="font-mono text-[11px] space-y-3">
<div class="text-center text-cyan-500/50 py-8">NO NODE SELECTED</div>
</div>
</div>
<div class="tech-panel p-4 flex-grow flex flex-col overflow-hidden">
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
OUTGOING CONNECTIONS</h3>
<div id="connections-list" class="font-mono text-[10px] overflow-y-auto flex-grow space-y-2">
<div class="text-center text-cyan-500/50 py-4">SELECT A NODE</div>
</div>
</div>
<div class="tech-panel h-32 p-4 relative overflow-hidden flex flex-col justify-between">
<div class="text-[10px] opacity-60 tracking-widest mb-1">BRAIN ACTIVITY</div>
<div class="flex items-end justify-between h-full gap-1 pt-2" id="activity-graph">
<div class="sys-bar w-1 bg-cyan-600 h-[30%]"></div>
<div class="sys-bar w-1 bg-cyan-500 h-[50%]"></div>
<div class="sys-bar w-1 bg-cyan-400 h-[80%]"></div>
<div class="sys-bar w-1 bg-cyan-300 h-[40%]"></div>
<div class="sys-bar w-1 bg-cyan-400 h-[60%]"></div>
<div class="sys-bar w-1 bg-cyan-500 h-[90%]"></div>
<div class="sys-bar w-1 bg-cyan-600 h-[45%]"></div>
<div class="sys-bar w-1 bg-cyan-500 h-[70%]"></div>
<div class="sys-bar w-1 bg-cyan-400 h-[30%]"></div>
<div class="sys-bar w-1 bg-cyan-300 h-[60%]"></div>
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
const NODES = [
{ id: 'core-1', name: 'Orchestrator Prime', type: 'knowledge', importance: 0.98, description: '中枢编排核心' },
{ id: 'core-2', name: 'Memory Vault', type: 'knowledge', importance: 0.91, description: '知识存储层' },
{ id: 'chat-1', name: 'Session Cluster', type: 'chat', importance: 0.89, description: '实时会话汇聚' },
{ id: 'chat-2', name: 'Operator Feed', type: 'chat', importance: 0.76, description: '操控台输入流' },
{ id: 'forum-1', name: 'Research Swarm', type: 'forum', importance: 0.81, description: '外部案例趋势池' },
{ id: 'forum-2', name: 'Threat Monitor', type: 'forum', importance: 0.72, description: '异常风险观察哨' },
{ id: 'sched-1', name: 'Sprint Grid', type: 'schedule', importance: 0.87, description: '执行排期矩阵' },
{ id: 'sched-2', name: 'Review Window', type: 'schedule', importance: 0.74, description: '评审验收窗口' },
{ id: 'bridge-1', name: 'Signal Bridge', type: 'knowledge', importance: 0.84, description: '跨域桥接节点' },
{ id: 'arch-1', name: 'Knowledge Archive', type: 'knowledge', importance: 0.66, description: '知识存档' },
{ id: 'thread-1', name: 'Conversation Thread', type: 'chat', importance: 0.62, description: '对话线程' },
{ id: 'signal-1', name: 'Forum Signal', type: 'forum', importance: 0.58, description: '外部论坛信号' },
{ id: 'lane-1', name: 'Schedule Lane', type: 'schedule', importance: 0.57, description: '排程支线' },
{ id: 'dormant-1', name: 'Dormant Trace', type: 'forum', importance: 0.45, description: '低优先级线索' },
];
const EDGES = [
{ id: 'e1', source: 'core-1', target: 'core-2', relation: '读取知识' },
{ id: 'e2', source: 'core-1', target: 'chat-1', relation: '接收会话' },
{ id: 'e3', source: 'core-1', target: 'sched-1', relation: '下发编排' },
{ id: 'e4', source: 'core-1', target: 'bridge-1', relation: '稳定桥接' },
{ id: 'e5', source: 'core-2', target: 'bridge-1', relation: '提供事实' },
{ id: 'e6', source: 'chat-1', target: 'chat-2', relation: '扩展输入' },
{ id: 'e7', source: 'chat-1', target: 'bridge-1', relation: '触发同步' },
{ id: 'e8', source: 'forum-1', target: 'bridge-1', relation: '补充趋势' },
{ id: 'e9', source: 'forum-2', target: 'bridge-1', relation: '反馈风险' },
{ id: 'e10', source: 'bridge-1', target: 'sched-1', relation: '投递任务' },
{ id: 'e11', source: 'sched-1', target: 'sched-2', relation: '进入评审' },
{ id: 'e12', source: 'forum-1', target: 'sched-2', relation: '支撑验证' },
{ id: 'e13', source: 'core-2', target: 'arch-1', relation: '归档事实' },
{ id: 'e14', source: 'bridge-1', target: 'arch-1', relation: '桥接引用' },
{ id: 'e15', source: 'chat-1', target: 'thread-1', relation: '拆分会话' },
{ id: 'e16', source: 'chat-2', target: 'thread-1', relation: '补充回执' },
{ id: 'e17', source: 'forum-1', target: 'signal-1', relation: '吸收案例' },
{ id: 'e18', source: 'sched-1', target: 'lane-1', relation: '拆分排期' },
{ id: 'e19', source: 'sched-2', target: 'lane-1', relation: '投递评审' },
{ id: 'e20', source: 'forum-2', target: 'dormant-1', relation: '弱风险' },
];
const TYPE_COLORS = {
knowledge: 0x00f3ff,
chat: 0xff6b9d,
forum: 0xa855f7,
schedule: 0xfbbf24,
};
const TYPE_LABELS = {
knowledge: '知识库',
chat: '对话',
forum: '论坛',
schedule: '日程',
};
let scene, camera, renderer;
let nodeObjects = new Map();
let edgeObjects = [];
let particleSystem;
let hoveredNodeId = null;
let selectedNodeId = null;
let mouseX = 0, mouseY = 0;
let targetX = 0, targetY = 0;
let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;
const nodeMeshes = [];
function updateTime() {
const now = new Date();
const el = document.getElementById('time-display');
if (el) el.innerText = now.toLocaleTimeString('en-US', { hour12: false });
}
setInterval(updateTime, 1000);
updateTime();
function init() {
const container = document.getElementById('brain-canvas-container');
const w = container.parentElement.clientWidth || 600;
const h = container.parentElement.clientHeight || 600;
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x020408, 0.0025);
camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 2000);
camera.position.z = 80;
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(w, h);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
container.appendChild(renderer.domElement);
const ambientLight = new THREE.AmbientLight(0x404060, 0.4);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0x00f3ff, 1.5, 300);
pointLight.position.set(0, 50, 50);
scene.add(pointLight);
createBackground();
createBrainCore();
createNodes();
createEdges();
createOrbitalRings();
document.addEventListener('mousemove', onDocumentMouseMove);
document.addEventListener('mousedown', onDocumentMouseDown);
document.addEventListener('mouseup', onDocumentMouseUp);
document.addEventListener('wheel', onDocumentWheel);
window.addEventListener('resize', onWindowResize);
updateMetrics();
updateNodeList();
setTimeout(() => {
const statusEl = document.getElementById('brain-status');
if (statusEl) {
statusEl.innerText = "ONLINE";
statusEl.classList.remove('text-red-500');
statusEl.classList.add('text-cyan-300', 'bg-cyan-500/20', 'border-cyan-400');
}
addLog(">> Brain matrix initialized.");
addLog(">> Neural pathways loaded.");
addLog(">> 14 nodes, 20 connections active.");
}, 1500);
animate();
}
function createBackground() {
const count = 1500;
const positions = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
positions[i * 3] = (Math.random() - 0.5) * 600;
positions[i * 3 + 1] = (Math.random() - 0.5) * 600;
positions[i * 3 + 2] = (Math.random() - 0.5) * 600;
const c = new THREE.Color();
c.setHSL(0.55 + Math.random() * 0.1, 0.7, 0.5 + Math.random() * 0.4);
colors[i * 3] = c.r;
colors[i * 3 + 1] = c.g;
colors[i * 3 + 2] = c.b;
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const mat = new THREE.PointsMaterial({
size: 0.5, vertexColors: true, transparent: true, opacity: 0.6,
blending: THREE.AdditiveBlending,
});
particleSystem = new THREE.Points(geo, mat);
scene.add(particleSystem);
}
function createBrainCore() {
const coreGeo = new THREE.IcosahedronGeometry(3, 2);
const coreMat = new THREE.MeshBasicMaterial({
color: 0x00f3ff,
wireframe: true,
transparent: true,
opacity: 0.12
});
const coreMesh = new THREE.Mesh(coreGeo, coreMat);
scene.add(coreMesh);
const innerGeo = new THREE.IcosahedronGeometry(2, 1);
const innerMat = new THREE.MeshBasicMaterial({
color: 0x00f3ff,
transparent: true,
opacity: 0.2
});
const innerMesh = new THREE.Mesh(innerGeo, innerMat);
scene.add(innerMesh);
}
function createHexShape(r) {
const shape = new THREE.Shape();
for (let i = 0; i < 6; i++) {
const a = (i / 6) * Math.PI * 2 - Math.PI / 6;
const x = Math.cos(a) * r, y = Math.sin(a) * r;
if (i === 0) shape.moveTo(x, y); else shape.lineTo(x, y);
}
shape.closePath();
return shape;
}
function createNodes() {
const angleStep = (Math.PI * 2) / NODES.length;
const radius = 30;
NODES.forEach((node, i) => {
const angle = i * angleStep;
const layer = Math.floor(i / 7);
const r = radius + layer * 18;
const x = Math.cos(angle) * r;
const z = Math.sin(angle) * r;
const y = (Math.random() - 0.5) * 8 + layer * 4;
const color = TYPE_COLORS[node.type];
const imp = node.importance;
const height = 3 + imp * 12;
const radius = 1.5 + imp * 1.5;
const group = new THREE.Group();
group.userData = { nodeId: node.id };
group.position.set(x, y, z);
const geo = new THREE.ExtrudeGeometry(createHexShape(radius), {
depth: height, bevelEnabled: true, bevelThickness: 0.2, bevelSize: 0.15, bevelSegments: 2,
});
geo.center();
const mat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.12, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geo, mat);
mesh.rotation.x = -Math.PI / 2;
group.add(mesh);
const edgesGeo = new THREE.EdgesGeometry(geo);
const edgesMat = new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.8 });
const edges = new THREE.LineSegments(edgesGeo, edgesMat);
edges.rotation.x = -Math.PI / 2;
group.add(edges);
const glowGeo = new THREE.SphereGeometry(radius * 0.6, 12, 12);
const glowMat = new THREE.MeshBasicMaterial({
color, transparent: true, opacity: 0.3 + imp * 0.4, blending: THREE.AdditiveBlending,
});
const glow = new THREE.Mesh(glowGeo, glowMat);
group.add(glow);
const topGeo = new THREE.CircleGeometry(radius * 0.4, 6);
const topMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.7, side: THREE.DoubleSide });
const top = new THREE.Mesh(topGeo, topMat);
top.rotation.x = -Math.PI / 2;
top.position.y = height / 2;
group.add(top);
group.rotation.y = Math.random() * Math.PI;
scene.add(group);
nodeObjects.set(node.id, group);
nodeMeshes.push(glow);
});
}
function createEdges() {
EDGES.forEach(edge => {
const src = nodeObjects.get(edge.source);
const tgt = nodeObjects.get(edge.target);
if (!src || !tgt) return;
const srcNode = NODES.find(n => n.id === edge.source);
const color = TYPE_COLORS[srcNode?.type || 'knowledge'];
const points = [src.position.clone(), tgt.position.clone()];
const geo = new THREE.BufferGeometry().setFromPoints(points);
const mat = new THREE.LineDashedMaterial({
color, transparent: true, opacity: 0.4,
dashSize: 1.5, gapSize: 1, blending: THREE.AdditiveBlending,
});
const line = new THREE.Line(geo, mat);
line.computeLineDistances();
scene.add(line);
edgeObjects.push(line);
});
}
function createOrbitalRings() {
const ring1Geo = new THREE.BufferGeometry();
const ring1Pos = new Float32Array(80 * 3);
for (let i = 0; i < 80; i++) {
const theta = (i / 80) * Math.PI * 2;
ring1Pos[i * 3] = Math.cos(theta) * 45;
ring1Pos[i * 3 + 1] = (Math.random() - 0.5) * 0.5;
ring1Pos[i * 3 + 2] = Math.sin(theta) * 45;
}
ring1Geo.setAttribute('position', new THREE.BufferAttribute(ring1Pos, 3));
const ring1Mat = new THREE.PointsMaterial({ color: 0x00f3ff, size: 0.3, transparent: true, opacity: 0.4 });
const ring1 = new THREE.Points(ring1Geo, ring1Mat);
ring1.userData = { speedX: 0.001, speedY: 0.002 };
scene.add(ring1);
const ring2Geo = new THREE.BufferGeometry();
const ring2Pos = new Float32Array(60 * 3);
for (let i = 0; i < 60; i++) {
const theta = (i / 60) * Math.PI * 2;
ring2Pos[i * 3] = (Math.random() - 0.5) * 0.5;
ring2Pos[i * 3 + 1] = Math.cos(theta) * 50;
ring2Pos[i * 3 + 2] = Math.sin(theta) * 50;
}
ring2Geo.setAttribute('position', new THREE.BufferAttribute(ring2Pos, 3));
const ring2Mat = new THREE.PointsMaterial({ color: 0xa855f7, size: 0.25, transparent: true, opacity: 0.3 });
const ring2 = new THREE.Points(ring2Geo, ring2Mat);
ring2.userData = { speedX: 0.002, speedY: 0.001 };
scene.add(ring2);
}
function onDocumentMouseMove(e) {
mouseX = e.clientX - windowHalfX;
mouseY = e.clientY - windowHalfY;
}
function onDocumentMouseDown(e) {}
function onDocumentMouseUp(e) {
if (hoveredNodeId) selectNode(hoveredNodeId);
}
function onDocumentWheel(e) {
camera.position.z += e.deltaY * 0.05;
camera.position.z = Math.max(30, Math.min(200, camera.position.z));
}
function onWindowResize() {
const container = document.getElementById('brain-canvas-container');
const w = container.parentElement.clientWidth || 600;
const h = container.parentElement.clientHeight || 600;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
function animate() {
requestAnimationFrame(animate);
const t = Date.now() * 0.001;
targetX = mouseX * 0.0005;
targetY = mouseY * 0.0005;
camera.rotation.y += (targetX - camera.rotation.y) * 0.05;
camera.rotation.x += (-targetY - camera.rotation.x) * 0.05;
particleSystem.rotation.y += 0.0001;
particleSystem.rotation.x += 0.00005;
nodeMeshes.forEach((mesh, i) => {
const pulse = Math.sin(t * 2 + i * 0.5) * 0.15;
mesh.scale.setScalar(1 + pulse);
});
edgeObjects.forEach(line => {
const mat = line.material;
mat.dashSize = 1.5 + Math.sin(t) * 0.3;
});
scene.children.forEach(child => {
if (child instanceof THREE.Points && child.userData.speedX) {
child.rotation.x += child.userData.speedX;
child.rotation.y += child.userData.speedY;
}
});
renderer.render(scene, camera);
}
function updateMetrics() {
document.getElementById('metric-nodes').textContent = NODES.length;
document.getElementById('metric-links').textContent = EDGES.length;
document.getElementById('nodes-bar').style.width = (NODES.length / 20 * 100) + '%';
document.getElementById('links-bar').style.width = (EDGES.length / 30 * 100) + '%';
const clusters = 3;
document.getElementById('metric-clusters').textContent = clusters;
document.getElementById('clusters-bar').style.width = (clusters / 5 * 100) + '%';
const coreCount = NODES.filter(n => n.importance >= 0.86).length;
const bridgeCount = NODES.filter(n => n.importance >= 0.7 && n.importance < 0.86).length;
document.getElementById('core-nodes').textContent = coreCount;
document.getElementById('bridge-nodes').textContent = bridgeCount;
document.getElementById('active-nodes-val').textContent = 'ACTIVE: ' + NODES.length;
}
function updateNodeList() {
const list = document.getElementById('node-list');
list.innerHTML = '';
NODES.forEach(node => {
const color = '#' + TYPE_COLORS[node.type].toString(16).padStart(6, '0');
const div = document.createElement('div');
div.className = 'flash-row p-2 flex justify-between items-center border-b border-cyan-500/10';
div.innerHTML = `
<div class="flex items-center gap-2">
<div class="w-1.5 h-1.5 rounded-full" style="background: ${color}; box-shadow: 0 0 4px ${color};"></div>
<span class="text-cyan-300 truncate max-w-[100px]">${node.name}</span>
</div>
<span class="opacity-50 text-[9px]">${Math.round(node.importance * 100)}%</span>
`;
div.onclick = () => selectNode(node.id);
list.appendChild(div);
});
}
function selectNode(nodeId) {
selectedNodeId = nodeId;
const node = NODES.find(n => n.id === nodeId);
if (!node) return;
document.getElementById('selected-val').textContent = 'SELECTED: ' + node.name;
const color = '#' + TYPE_COLORS[node.type].toString(16).padStart(6, '0');
const detail = document.getElementById('node-detail');
detail.innerHTML = `
<div class="mb-3 text-center">
<div class="text-[9px] opacity-50 uppercase tracking-widest">${TYPE_LABELS[node.type]}</div>
<div class="text-lg font-bold text-cyan-100 tracking-wider">${node.name}</div>
</div>
<div class="space-y-2">
<div class="flex justify-between"><span class="opacity-60">IMPORTANCE</span><span class="text-cyan-300">${Math.round(node.importance * 100)}%</span></div>
<div class="flex justify-between"><span class="opacity-60">DESCRIPTION</span></div>
<div class="text-cyan-300/80 text-[10px] pl-2 border-l border-cyan-500/30">${node.description}</div>
</div>
`;
const conns = EDGES.filter(e => e.source === nodeId || e.target === nodeId);
const list = document.getElementById('connections-list');
list.innerHTML = '';
if (conns.length === 0) {
list.innerHTML = '<div class="text-center text-cyan-500/50 py-4">NO CONNECTIONS</div>';
} else {
conns.forEach(conn => {
const otherId = conn.source === nodeId ? conn.target : conn.source;
const other = NODES.find(n => n.id === otherId);
const otherColor = '#' + TYPE_COLORS[other?.type || 'knowledge'].toString(16).padStart(6, '0');
const div = document.createElement('div');
div.className = 'border-b border-cyan-500/10 pb-1 mb-1';
div.innerHTML = `
<div class="flex items-center gap-1 text-[9px]">
<span class="text-cyan-500">${conn.relation}</span>
<span class="opacity-30">→</span>
<span class="w-2 h-2 rounded-full inline-block" style="background: ${otherColor}; box-shadow: 0 0 4px ${otherColor};"></span>
<span class="text-cyan-300">${other?.name || ''}</span>
</div>
`;
list.appendChild(div);
});
}
document.getElementById('detail-panel').classList.add('active');
addLog('>> Node selected: ' + node.name);
}
function addLog(msg) {
const terminal = document.getElementById('brain-terminal');
if (!terminal) return;
const div = document.createElement('div');
div.className = 'border-l-2 border-cyan-800 pl-2 py-0.5';
div.innerHTML = `<span class="text-[10px] text-cyan-600 mr-2 font-bold">[${new Date().toLocaleTimeString('en-US', { hour12: false })}]</span><span class="text-xs font-mono text-cyan-300/80">${msg}</span>`;
terminal.appendChild(div);
if (terminal.children.length > 15) terminal.removeChild(terminal.firstChild);
terminal.scrollTop = terminal.scrollHeight;
}
function updateActivityGraph() {
const bars = document.querySelectorAll('#activity-graph .sys-bar');
bars.forEach(bar => {
bar.style.height = Math.floor(Math.random() * 80 + 10) + '%';
});
}
setInterval(updateActivityGraph, 500);
document.addEventListener('mousemove', (e) => {
const container = document.getElementById('brain-canvas-container');
if (!container) return;
const rect = container.getBoundingClientRect();
if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) {
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera({ x, y }, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
let found = null;
for (const hit of intersects) {
let obj = hit.object;
while (obj) {
if (obj.userData?.nodeId) { found = obj.userData.nodeId; break; }
obj = obj.parent;
}
if (found) break;
}
hoveredNodeId = found;
container.style.cursor = found ? 'pointer' : 'grab';
}
});
init();
})();
</script>
</body>
</html>