48 lines
2.5 KiB
JavaScript
48 lines
2.5 KiB
JavaScript
// hero.js — a live 3D lattice for the hero: a slab of nodes (a via-lattice motif) with near-neighbour
|
|
// links, slowly rotating and parallaxing to the pointer. Off-white nodes, a scatter of lime. three.js.
|
|
import * as THREE from 'three';
|
|
|
|
const canvas = document.getElementById('hero-canvas');
|
|
if (canvas) {
|
|
const r = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
|
|
r.setPixelRatio(Math.min(devicePixelRatio, 2));
|
|
const scene = new THREE.Scene();
|
|
const cam = new THREE.PerspectiveCamera(42, 1, 0.1, 100);
|
|
cam.position.set(0, 0, 15);
|
|
|
|
// scatter nodes across a wide, shallow slab
|
|
const N = 260, W = 26, H = 12, D = 5, pts = [];
|
|
for (let i = 0; i < N; i++) pts.push(new THREE.Vector3((Math.random() - 0.5) * W, (Math.random() - 0.5) * H, (Math.random() - 0.5) * D));
|
|
|
|
// links between near neighbours
|
|
const linePos = [];
|
|
for (let i = 0; i < N; i++) for (let j = i + 1; j < N; j++) {
|
|
if (pts[i].distanceTo(pts[j]) < 2.4) { linePos.push(pts[i].x, pts[i].y, pts[i].z, pts[j].x, pts[j].y, pts[j].z); }
|
|
}
|
|
const lg = new THREE.BufferGeometry();
|
|
lg.setAttribute('position', new THREE.Float32BufferAttribute(linePos, 3));
|
|
const lines = new THREE.LineSegments(lg, new THREE.LineBasicMaterial({ color: 0xf4f3ee, transparent: true, opacity: 0.08 }));
|
|
|
|
// node points — most off-white, a few lime accents
|
|
const pg = new THREE.BufferGeometry();
|
|
pg.setAttribute('position', new THREE.Float32BufferAttribute(pts.flatMap((p) => [p.x, p.y, p.z]), 3));
|
|
const colors = [];
|
|
for (let i = 0; i < N; i++) { const lime = Math.random() < 0.12; colors.push(lime ? 0.78 : 0.96, lime ? 1.0 : 0.95, lime ? 0.18 : 0.93); }
|
|
pg.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
const points = new THREE.Points(pg, new THREE.PointsMaterial({ size: 0.13, vertexColors: true, transparent: true, opacity: 0.9 }));
|
|
|
|
const group = new THREE.Group(); group.add(lines); group.add(points); scene.add(group);
|
|
|
|
const resize = () => { const w = canvas.clientWidth, h = canvas.clientHeight; r.setSize(w, h, false); cam.aspect = w / h; cam.updateProjectionMatrix(); };
|
|
resize(); addEventListener('resize', resize);
|
|
|
|
let mx = 0, my = 0, tx = 0, ty = 0, tick = 0;
|
|
addEventListener('pointermove', (e) => { tx = (e.clientX / innerWidth - 0.5); ty = (e.clientY / innerHeight - 0.5); });
|
|
(function loop() {
|
|
requestAnimationFrame(loop); tick += 0.0016;
|
|
mx += (tx - mx) * 0.04; my += (ty - my) * 0.04;
|
|
group.rotation.y = tick + mx * 0.5;
|
|
group.rotation.x = Math.sin(tick * 0.6) * 0.08 + my * 0.3;
|
|
r.render(scene, cam);
|
|
})();
|
|
}
|