RAG-PSYCH / api /static /app.js
arjun10g's picture
Initial deploy to Hugging Face Spaces
08fc97e
// Three.js neural-particle hero scene.
//
// A drifting cloud of particles connected by short lines whenever any two
// particles fall within a threshold distance. Loose neuro/network theme,
// keeps memory and CPU low (no model loads, no shaders beyond the
// PointsMaterial built-in). Resizes with the viewport and pauses when
// the tab is hidden so it doesn't drain battery in the background.
import * as THREE from "three";
const canvas = document.getElementById("neural-bg");
if (canvas) {
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 1000);
camera.position.z = 60;
const PARTICLE_COUNT = 140;
const FIELD_SIZE = 80;
const LINK_DISTANCE = 14;
const positions = new Float32Array(PARTICLE_COUNT * 3);
const velocities = new Float32Array(PARTICLE_COUNT * 3);
for (let i = 0; i < PARTICLE_COUNT; i++) {
positions[i * 3] = (Math.random() - 0.5) * FIELD_SIZE;
positions[i * 3 + 1] = (Math.random() - 0.5) * FIELD_SIZE;
positions[i * 3 + 2] = (Math.random() - 0.5) * FIELD_SIZE * 0.6;
velocities[i * 3] = (Math.random() - 0.5) * 0.04;
velocities[i * 3 + 1] = (Math.random() - 0.5) * 0.04;
velocities[i * 3 + 2] = (Math.random() - 0.5) * 0.04;
}
const pGeom = new THREE.BufferGeometry();
pGeom.setAttribute("position", new THREE.BufferAttribute(positions, 3));
const pMat = new THREE.PointsMaterial({
size: 1.4,
color: 0x67e8f9, // cyan-300
transparent: true,
opacity: 0.85,
sizeAttenuation: true,
});
const points = new THREE.Points(pGeom, pMat);
scene.add(points);
// Pre-allocate the lines geometry to a generous upper bound so we don't
// re-allocate buffers every frame. Capacity is the worst-case pair count.
const MAX_LINKS = PARTICLE_COUNT * 8;
const linePositions = new Float32Array(MAX_LINKS * 2 * 3);
const lineGeom = new THREE.BufferGeometry();
lineGeom.setAttribute("position", new THREE.BufferAttribute(linePositions, 3));
const lineMat = new THREE.LineBasicMaterial({
color: 0xe879f9, // fuchsia-400
transparent: true,
opacity: 0.18,
});
const lines = new THREE.LineSegments(lineGeom, lineMat);
scene.add(lines);
function resize() {
const w = window.innerWidth, h = window.innerHeight;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
resize();
window.addEventListener("resize", resize);
let running = true;
document.addEventListener("visibilitychange", () => {
running = document.visibilityState === "visible";
if (running) animate();
});
function animate() {
if (!running) return;
requestAnimationFrame(animate);
// Drift particles, wrap around the field box.
for (let i = 0; i < PARTICLE_COUNT; i++) {
for (let axis = 0; axis < 3; axis++) {
const idx = i * 3 + axis;
positions[idx] += velocities[idx];
const limit = axis === 2 ? FIELD_SIZE * 0.3 : FIELD_SIZE * 0.5;
if (positions[idx] > limit) positions[idx] = -limit;
else if (positions[idx] < -limit) positions[idx] = limit;
}
}
pGeom.attributes.position.needsUpdate = true;
// Recompute neighbor links. O(N²) but N=140 → ~20K dist checks/frame, fine.
let written = 0;
for (let i = 0; i < PARTICLE_COUNT; i++) {
for (let j = i + 1; j < PARTICLE_COUNT; j++) {
const dx = positions[i*3] - positions[j*3];
const dy = positions[i*3+1] - positions[j*3+1];
const dz = positions[i*3+2] - positions[j*3+2];
const d2 = dx*dx + dy*dy + dz*dz;
if (d2 < LINK_DISTANCE * LINK_DISTANCE && written < MAX_LINKS) {
const k = written * 6;
linePositions[k] = positions[i*3];
linePositions[k+1] = positions[i*3+1];
linePositions[k+2] = positions[i*3+2];
linePositions[k+3] = positions[j*3];
linePositions[k+4] = positions[j*3+1];
linePositions[k+5] = positions[j*3+2];
written++;
}
}
}
lineGeom.setDrawRange(0, written * 2);
lineGeom.attributes.position.needsUpdate = true;
points.rotation.y += 0.0008;
lines.rotation.y += 0.0008;
renderer.render(scene, camera);
}
animate();
}