Menu
Public repository / 7c98a6fe64be6cf4971a37c541e219b5dfcd684b
AkurAI-Platform / frontend/spatial-scene.js
Latest change 7c98a6fe64be6cf4971a37c541e219b5dfcd684b - OLI-105: baseline snapshot of live platform.olibuijr.com frontend (source was missing from repo) by DevLead
import * as THREE from "/vendor/three.module.min.js";
const stage = document.getElementById("spatial-stage");
const canvas = stage?.querySelector(".spatial-canvas");
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
if (stage && canvas && !reducedMotion.matches) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(38, 1, 0.1, 100);
camera.position.set(0, 0, 7);
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true, powerPreference: "high-performance" });
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.75));
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
const rig = new THREE.Group();
scene.add(rig);
const glass = new THREE.MeshPhysicalMaterial({
color: 0xb9e8ff,
metalness: 0.08,
roughness: 0.12,
transmission: 0.82,
thickness: 0.55,
transparent: true,
opacity: 0.72,
iridescence: 0.35,
iridescenceIOR: 1.25,
clearcoat: 1,
clearcoatRoughness: 0.08,
});
const titanium = new THREE.MeshStandardMaterial({ color: 0xd9e2ec, metalness: 0.92, roughness: 0.2 });
const glow = new THREE.MeshStandardMaterial({ color: 0x7ddfff, emissive: 0x159ed4, emissiveIntensity: 5, metalness: 0.1, roughness: 0.15 });
const shell = new THREE.Mesh(new THREE.IcosahedronGeometry(1.48, 1), glass);
shell.scale.set(1.18, 1, 0.55);
rig.add(shell);
const core = new THREE.Mesh(new THREE.OctahedronGeometry(0.72, 0), glow);
core.rotation.set(Math.PI / 4, 0, Math.PI / 4);
rig.add(core);
for (const [radius, tube, rotation] of [
[1.83, 0.028, [1.1, 0.25, 0.3]],
[2.08, 0.022, [0.2, 1.15, -0.4]],
[2.32, 0.018, [0.6, -0.45, 1.15]],
]) {
const orbit = new THREE.Mesh(new THREE.TorusGeometry(radius, tube, 10, 120), titanium);
orbit.rotation.set(...rotation);
rig.add(orbit);
}
const nodes = [];
for (let index = 0; index < 9; index += 1) {
const node = new THREE.Mesh(new THREE.SphereGeometry(index === 0 ? 0.12 : 0.075, 18, 18), index === 0 ? glow : titanium);
const angle = (index / 9) * Math.PI * 2;
node.userData = { angle, speed: 0.22 + (index % 3) * 0.055, radius: 1.82 + (index % 3) * 0.24 };
rig.add(node);
nodes.push(node);
}
scene.add(new THREE.HemisphereLight(0xbcecff, 0x0a0d18, 2.2));
const key = new THREE.DirectionalLight(0xffffff, 6);
key.position.set(4, 5, 6);
scene.add(key);
const edge = new THREE.PointLight(0x755dff, 18, 14);
edge.position.set(-3, -2, 3);
scene.add(edge);
const pointer = new THREE.Vector2();
let scrollProgress = 0;
let frame = 0;
function resize() {
const { width, height } = stage.getBoundingClientRect();
renderer.setSize(width, height, false);
camera.aspect = width / Math.max(height, 1);
camera.updateProjectionMatrix();
}
function updateScroll() {
const rect = stage.getBoundingClientRect();
scrollProgress = THREE.MathUtils.clamp((innerHeight - rect.top) / (innerHeight + rect.height), 0, 1);
}
stage.addEventListener("pointermove", (event) => {
const rect = stage.getBoundingClientRect();
pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
}, { passive: true });
stage.addEventListener("pointerleave", () => pointer.set(0, 0), { passive: true });
addEventListener("resize", resize, { passive: true });
addEventListener("scroll", updateScroll, { passive: true });
const clock = new THREE.Clock();
function render() {
const time = clock.getElapsedTime();
rig.rotation.y += (pointer.x * 0.24 + scrollProgress * 1.15 - rig.rotation.y) * 0.035;
rig.rotation.x += (-pointer.y * 0.16 + scrollProgress * 0.28 - rig.rotation.x) * 0.035;
rig.position.y = Math.sin(time * 0.72) * 0.08;
shell.rotation.z = time * 0.055;
core.rotation.y = time * 0.42;
core.scale.setScalar(1 + Math.sin(time * 2.1) * 0.045);
nodes.forEach((node) => {
const angle = node.userData.angle + time * node.userData.speed + scrollProgress * Math.PI * 2;
node.position.set(Math.cos(angle) * node.userData.radius, Math.sin(angle * 1.3) * 0.8, Math.sin(angle) * node.userData.radius * 0.38);
});
stage.style.setProperty("--pointer-x", pointer.x.toFixed(3));
stage.style.setProperty("--pointer-y", pointer.y.toFixed(3));
stage.style.setProperty("--scroll-progress", scrollProgress.toFixed(3));
renderer.render(scene, camera);
frame = requestAnimationFrame(render);
}
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !frame) render();
if (!entry.isIntersecting && frame) {
cancelAnimationFrame(frame);
frame = 0;
}
});
resize();
updateScroll();
observer.observe(stage);
stage.classList.add("is-spatial-ready");
}