NODE - RETROSCOPIC CRT SHADER SOURCE CODE
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>3D Net — Live Reconnecting Network</title>
<style>
html, body {
margin: 0;
overflow: hidden;
width: 100%;
height: 100%;
background: #000;
font-family: 'Segoe UI', 'Inter', system-ui, -apple-system, sans-serif;
}
canvas {
display: block; outline: none; touch-action: none;
width:100vw;
height:100vh;
filter: grayscale(1) sepia(1) hue-rotate(80deg) saturate(5) brightness(1.15) contrast(1.2);
position: relative;
z-index: 2;
}
/* Retroscopic Overlay */
#scope {
position: fixed;
inset: 0;
z-index: 4;
pointer-events: none;
display: block;
}
#scope>div {
position: absolute;
inset: 0;
}
#scope .glow {
background: radial-gradient(circle at 50% 46%, rgba(30, 255, 130, .08), rgba(0, 0, 0, 0) 62%);
animation: nvflick 3.6s steps(24) infinite;
}
#scope .scan {
background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0) 0 2px, rgba(0, 25, 8, .45) 2px 3px);
animation: nvscan 7s linear infinite;
}
#scope .vig {
box-shadow: inset 0 0 200px 50px rgba(0, 0, 0, .8);
}
#scope .mask {
background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0) 54%, rgba(0, 0, 0, .5) 72%, rgba(0, 0, 0, .94) 100%);
}
@keyframes nvscan {
from { background-position: 0 0; }
to { background-position: 0 240px; }
}
@keyframes nvflick {
0%, 100% { opacity: .55; }
40% { opacity: .9; }
62% { opacity: .4; }
80% { opacity: .75; }
}
</style>
</head>
<body>
<div id="scope">
<div class="glow"></div>
<div class="scan"></div>
<div class="vig"></div>
<div class="mask"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ---------------------------------------------------------------
// Core setup
// ---------------------------------------------------------------
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.FogExp2(0x000000, 0.0022);
const camera = new THREE.PerspectiveCamera(
65, window.innerWidth / window.innerHeight, 0.1, 2000
);
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// ---------------------------------------------------------------
// Lightweight custom orbit controls (drag to rotate, wheel to zoom,
// gentle auto-rotate when idle) — avoids relying on an external
// OrbitControls module that may be blocked by CSP.
// ---------------------------------------------------------------
const orbit = {
radius: 630,
minRadius: 180,
maxRadius: 1440,
theta: 0,
phi: Math.PI / 2.35,
autoRotate: true,
autoRotateSpeed: 0.06,
dragging: false,
lastX: 0,
lastY: 0
};
function updateCameraFromOrbit() {
const sinPhi = Math.sin(orbit.phi);
camera.position.set(
orbit.radius * sinPhi * Math.sin(orbit.theta),
orbit.radius * Math.cos(orbit.phi),
orbit.radius * sinPhi * Math.cos(orbit.theta)
);
camera.lookAt(0, 0, 0);
}
updateCameraFromOrbit();
function onDragStart(x, y) {
orbit.dragging = true;
orbit.lastX = x;
orbit.lastY = y;
}
function onDragMove(x, y) {
if (!orbit.dragging) return;
const dx = x - orbit.lastX;
const dy = y - orbit.lastY;
orbit.theta -= dx * 0.0045;
orbit.phi -= dy * 0.0045;
orbit.phi = Math.max(0.25, Math.min(Math.PI - 0.25, orbit.phi));
orbit.lastX = x;
orbit.lastY = y;
}
function onDragEnd() { orbit.dragging = false; }
renderer.domElement.addEventListener('mousedown', (e) => onDragStart(e.clientX, e.clientY));
window.addEventListener('mousemove', (e) => onDragMove(e.clientX, e.clientY));
window.addEventListener('mouseup', onDragEnd);
renderer.domElement.addEventListener('touchstart', (e) => {
if (e.touches.length === 1) onDragStart(e.touches[0].clientX, e.touches[0].clientY);
}, { passive: true });
renderer.domElement.addEventListener('touchmove', (e) => {
if (e.touches.length === 1) onDragMove(e.touches[0].clientX, e.touches[0].clientY);
}, { passive: true });
renderer.domElement.addEventListener('touchend', onDragEnd);
renderer.domElement.addEventListener('wheel', (e) => {
e.preventDefault();
orbit.radius += e.deltaY * 0.12;
orbit.radius = Math.max(orbit.minRadius, Math.min(orbit.maxRadius, orbit.radius));
}, { passive: false });
// ---------------------------------------------------------------
// Lights
// ---------------------------------------------------------------
scene.add(new THREE.AmbientLight(0xffffff, 0.35));
const keyLight = new THREE.DirectionalLight(0xffffff, 0.7);
keyLight.position.set(1, 2, 1.5);
scene.add(keyLight);
const backLight = new THREE.PointLight(0xff8855, 0.7, 500);
backLight.position.set(-60, 40, -80);
scene.add(backLight);
const fillLight = new THREE.PointLight(0x6688ff, 0.5, 500);
fillLight.position.set(0, -120, 0);
scene.add(fillLight);
const movingLight = new THREE.PointLight(0xff9c66, 1.1, 400);
scene.add(movingLight);
// ---------------------------------------------------------------
// Node field: multiple concentric "shells" (layers), evenly
// distributed with a spherical Fibonacci lattice so the network
// reads as a dense organic brain-like volume.
// ---------------------------------------------------------------
const SHELLS = 7;
const BASE_RADIUS = 102;
const SHELL_GAP = 45;
const BASE_COUNT = 34;
const COUNT_GROWTH = 10;
const nodes = [];
const nodeGroup = new THREE.Group();
scene.add(nodeGroup);
function fibonacciSpherePoints(count, radius) {
const pts = [];
const golden = Math.PI * (3 - Math.sqrt(5));
for (let i = 0; i < count; i++) {
const y = 1 - (i / (count - 1)) * 2;
const r = Math.sqrt(1 - y * y);
const theta = golden * i;
const x = Math.cos(theta) * r;
const z = Math.sin(theta) * r;
pts.push(new THREE.Vector3(x * radius, y * radius, z * radius));
}
return pts;
}
function shellColor(shellIdx, total) {
const hue = 0.06 + (shellIdx / (total - 1)) * 0.5;
const color = new THREE.Color();
color.setHSL(hue, 0.75, 0.58);
return color;
}
const sharedNodeGeo = new THREE.SphereGeometry(1, 12, 10);
for (let s = 0; s < SHELLS; s++) {
const radius = BASE_RADIUS + s * SHELL_GAP;
const count = BASE_COUNT + s * COUNT_GROWTH;
const color = shellColor(s, SHELLS);
const points = fibonacciSpherePoints(count, radius);
const mat = new THREE.MeshPhongMaterial({
color: color,
shininess: 90,
emissive: color.clone().multiplyScalar(0.35),
flatShading: false
});
points.forEach((p) => {
const jitter = 1 + (Math.random() - 0.5) * 0.06;
p.multiplyScalar(jitter);
const size = 0.55 + Math.random() * 0.9;
const mesh = new THREE.Mesh(sharedNodeGeo, mat);
mesh.position.copy(p);
mesh.scale.setScalar(size);
mesh.userData.baseScale = size;
mesh.userData.pulse = 0;
nodeGroup.add(mesh);
nodes.push({ mesh, shell: s, color });
});
}
// ---------------------------------------------------------------
// Dynamic connection pool
// ---------------------------------------------------------------
const MAX_LINK_DIST = 46;
const LINK_POOL_SIZE = window.innerWidth < 700 ? 320 : 650;
const FADE_TIME = 0.9;
const MIN_LIFE = 2.5;
const MAX_LIFE = 7.5;
const linkGeometry = new THREE.BufferGeometry();
const linkPositions = new Float32Array(LINK_POOL_SIZE * 2 * 3);
const linkColors = new Float32Array(LINK_POOL_SIZE * 2 * 3);
linkGeometry.setAttribute('position', new THREE.BufferAttribute(linkPositions, 3));
linkGeometry.setAttribute('color', new THREE.BufferAttribute(linkColors, 3));
const linkMaterial = new THREE.LineBasicMaterial({
vertexColors: true,
transparent: true,
opacity: 0.85,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const linkLines = new THREE.LineSegments(linkGeometry, linkMaterial);
scene.add(linkLines);
function randomNearbyPair() {
let a = Math.floor(Math.random() * nodes.length);
for (let tries = 0; tries < 10; tries++) {
const b = Math.floor(Math.random() * nodes.length);
if (b === a) continue;
const dist = nodes[a].mesh.position.distanceTo(nodes[b].mesh.position);
if (dist < MAX_LINK_DIST || Math.random() < 0.04) return [a, b];
}
return [a, (a + 1) % nodes.length];
}
function makeLink() {
const [a, b] = randomNearbyPair();
return { a, b, age: 0, life: MIN_LIFE + Math.random() * (MAX_LIFE - MIN_LIFE) };
}
const links = [];
for (let i = 0; i < LINK_POOL_SIZE; i++) links.push(makeLink());
let rewireEvents = 0;
function linkAlpha(link) {
const t = link.age, life = link.life;
if (t < FADE_TIME) return t / FADE_TIME;
if (t > life - FADE_TIME) return Math.max(0, (life - t) / FADE_TIME);
return 1;
}
function writeLinkVertex(offset, position, color, alpha) {
linkPositions[offset * 3] = position.x;
linkPositions[offset * 3 + 1] = position.y;
linkPositions[offset * 3 + 2] = position.z;
linkColors[offset * 3] = color.r * alpha;
linkColors[offset * 3 + 1] = color.g * alpha;
linkColors[offset * 3 + 2] = color.b * alpha;
}
function rewireLink(link) {
const [a, b] = randomNearbyPair();
link.a = a;
link.b = b;
link.age = 0;
link.life = MIN_LIFE + Math.random() * (MAX_LIFE - MIN_LIFE);
rewireEvents++;
}
function rewireLinksTouchingNode(nodeIdx) {
let count = 0;
for (const link of links) {
if (link.a === nodeIdx || link.b === nodeIdx) {
rewireLink(link);
count++;
if (count > 60) break;
}
}
}
function burstRandomRewire(fraction) {
const n = Math.floor(links.length * fraction);
for (let i = 0; i < n; i++) rewireLink(links[Math.floor(Math.random() * links.length)]);
}
// ---------------------------------------------------------------
// Traveling signal pulses
// ---------------------------------------------------------------
const PULSE_COUNT = Math.floor(LINK_POOL_SIZE * 0.4);
const pulseGeometry = new THREE.BufferGeometry();
const pulsePositions = new Float32Array(PULSE_COUNT * 3);
const pulseColors = new Float32Array(PULSE_COUNT * 3);
pulseGeometry.setAttribute('position', new THREE.BufferAttribute(pulsePositions, 3));
pulseGeometry.setAttribute('color', new THREE.BufferAttribute(pulseColors, 3));
const pulseMaterial = new THREE.PointsMaterial({
size: 2.1,
vertexColors: true,
transparent: true,
opacity: 0.95,
blending: THREE.AdditiveBlending,
depthWrite: false,
sizeAttenuation: true
});
const pulsePoints = new THREE.Points(pulseGeometry, pulseMaterial);
scene.add(pulsePoints);
const pulseAssignments = [];
for (let i = 0; i < PULSE_COUNT; i++) {
pulseAssignments.push({
linkIndex: Math.floor(Math.random() * links.length),
t: Math.random(),
speed: 0.25 + Math.random() * 0.5
});
}
// ---------------------------------------------------------------
// Outer aura + starfield
// ---------------------------------------------------------------
const starGeometry = new THREE.BufferGeometry();
const starCount = 2400;
const starPositions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
starPositions[i * 3] = (Math.random() - 0.5) * 900;
starPositions[i * 3 + 1] = (Math.random() - 0.5) * 600;
starPositions[i * 3 + 2] = (Math.random() - 0.5) * 900;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
const starMaterial = new THREE.PointsMaterial({ color: 0xaaccff, size: 0.35, transparent: true, opacity: 0.45 });
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
const auraGeo = new THREE.SphereGeometry(BASE_RADIUS + SHELL_GAP * (SHELLS - 1) + 14, 40, 24);
const auraMat = new THREE.MeshBasicMaterial({ color: 0x336699, wireframe: true, transparent: true, opacity: 0.055 });
const aura = new THREE.Mesh(auraGeo, auraMat);
scene.add(aura);
// ---------------------------------------------------------------
// Node click / tap interaction
// ---------------------------------------------------------------
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
let autoBurstEnabled = true;
let clickMoved = false;
function flashNode(idx) { nodes[idx].mesh.userData.pulse = 1; }
function handleTap(clientX, clientY) {
pointer.x = (clientX / window.innerWidth) * 2 - 1;
pointer.y = -(clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(nodeGroup.children, false);
if (intersects.length > 0) {
const idx = nodeGroup.children.indexOf(intersects[0].object);
if (idx !== -1) {
rewireLinksTouchingNode(idx);
flashNode(idx);
}
} else {
burstRandomRewire(0.16);
}
}
renderer.domElement.addEventListener('mousedown', () => { clickMoved = false; });
renderer.domElement.addEventListener('mousemove', () => { if (orbit.dragging) clickMoved = true; });
renderer.domElement.addEventListener('mouseup', (e) => { if (!clickMoved) handleTap(e.clientX, e.clientY); });
renderer.domElement.addEventListener('touchend', (e) => {
if (e.changedTouches.length === 0) return;
const t = e.changedTouches[0];
handleTap(t.clientX, t.clientY);
});
// ---------------------------------------------------------------
// Animation loop
// ---------------------------------------------------------------
const clock = new THREE.Clock();
let rewireAccumTime = 0;
let periodicBurstTimer = 0;
function updateLinks(dt) {
for (let i = 0; i < links.length; i++) {
const link = links[i];
link.age += dt;
if (link.age >= link.life) rewireLink(link);
const alpha = linkAlpha(link);
const nodeA = nodes[link.a].mesh.position;
const nodeB = nodes[link.b].mesh.position;
const mixed = nodes[link.a].color.clone().lerp(nodes[link.b].color, 0.5);
writeLinkVertex(i * 2, nodeA, mixed, alpha);
writeLinkVertex(i * 2 + 1, nodeB, mixed, alpha);
}
linkGeometry.attributes.position.needsUpdate = true;
linkGeometry.attributes.color.needsUpdate = true;
}
function updatePulses(dt) {
for (let i = 0; i < pulseAssignments.length; i++) {
const pa = pulseAssignments[i];
pa.t += dt * pa.speed;
if (pa.t > 1) {
pa.t = 0;
pa.linkIndex = Math.floor(Math.random() * links.length);
}
const link = links[pa.linkIndex];
const nodeA = nodes[link.a].mesh.position;
const nodeB = nodes[link.b].mesh.position;
const alpha = linkAlpha(link);
pulsePositions[i * 3] = nodeA.x + (nodeB.x - nodeA.x) * pa.t;
pulsePositions[i * 3 + 1] = nodeA.y + (nodeB.y - nodeA.y) * pa.t;
pulsePositions[i * 3 + 2] = nodeA.z + (nodeB.z - nodeA.z) * pa.t;
const glow = alpha * (0.6 + 0.4 * Math.sin(pa.t * Math.PI));
pulseColors[i * 3] = 1.0 * glow;
pulseColors[i * 3 + 1] = 0.68 * glow;
pulseColors[i * 3 + 2] = 0.42 * glow;
}
pulseGeometry.attributes.position.needsUpdate = true;
pulseGeometry.attributes.color.needsUpdate = true;
}
function updateNodePulses(dt) {
for (const n of nodes) {
if (n.mesh.userData.pulse > 0) {
n.mesh.userData.pulse -= dt * 2.2;
const p = Math.max(0, n.mesh.userData.pulse);
n.mesh.scale.setScalar(n.mesh.userData.baseScale * (1 + p * 1.8));
} else if (n.mesh.scale.x !== n.mesh.userData.baseScale) {
n.mesh.scale.setScalar(n.mesh.userData.baseScale);
}
}
}
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05);
const elapsed = clock.getElapsedTime();
if (!orbit.dragging && orbit.autoRotate) orbit.theta += orbit.autoRotateSpeed * dt;
updateCameraFromOrbit();
updateLinks(dt);
updatePulses(dt);
updateNodePulses(dt);
nodeGroup.rotation.y += 0.0006;
stars.rotation.y += 0.0003;
stars.rotation.x += 0.00015;
aura.rotation.y -= 0.0004;
aura.rotation.x += 0.0002;
movingLight.position.set(
70 * Math.sin(elapsed * 0.35),
40 * Math.cos(elapsed * 0.27),
70 * Math.cos(elapsed * 0.35)
);
movingLight.intensity = 0.9 + Math.sin(elapsed * 1.4) * 0.3;
if (autoBurstEnabled) {
periodicBurstTimer += dt;
if (periodicBurstTimer > 4.5) {
periodicBurstTimer = 0;
burstRandomRewire(0.05);
}
}
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
const width = window.innerWidth;
const height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
console.log('3D Net ready | nodes: ' + nodes.length + ' | link pool: ' + LINK_POOL_SIZE + ' | pulses: ' + PULSE_COUNT);
</script>
</body>
</html>