Crear una cuenta

Ingresa tu email y contraseña para registrarte

¿Ya tienes una cuenta? Iniciar Sesión

const canvas = document.getElementById('network3d-canvas'); if (!canvas) { console.error('Canvas network3d-canvas not found'); return false; } const ctx = canvas.getContext('2d'); let w, h; let boxes = []; let connections = []; let particles = []; let time = 0; let animationId = null; const config = { boxCount: 15, boxSize: 60, connectionDistance: 500, rotationSpeed: 0.005, moveSpeed: 0.8, particleSpeed: 0.03, particleSpawnChance: 0.15, bounds: { x: 500, y: 500, z: 400 } }; function project3D(x, y, z) { const distance = 500; const scale = distance / (distance + z); return { x: x * scale + w / 2, y: y * scale + h / 2, scale: Math.max(0.1, scale) }; } class Box3D { constructor() { this.x = (Math.random() - 0.5) * config.bounds.x * 1.6; this.y = (Math.random() - 0.5) * config.bounds.y * 1.6; this.z = (Math.random() - 0.5) * config.bounds.z * 1.6; this.size = config.boxSize + Math.random() * 10; this.rotationX = Math.random() * Math.PI * 2; this.rotationY = Math.random() * Math.PI * 2; this.rotationZ = Math.random() * Math.PI * 2; this.rotSpeedX = (Math.random() - 0.5) * config.rotationSpeed; this.rotSpeedY = (Math.random() - 0.5) * config.rotationSpeed; this.rotSpeedZ = (Math.random() - 0.5) * config.rotationSpeed; this.targetX = (Math.random() - 0.5) * config.bounds.x * 1.6; this.targetY = (Math.random() - 0.5) * config.bounds.y * 1.6; this.targetZ = (Math.random() - 0.5) * config.bounds.z * 1.6; this.color = `hsl(${190 + Math.random() * 70}, 85%, ${70 + Math.random() * 20}%)`; } chooseNewTarget() { this.targetX = (Math.random() - 0.5) * config.bounds.x * 1.6; this.targetY = (Math.random() - 0.5) * config.bounds.y * 1.6; this.targetZ = (Math.random() - 0.5) * config.bounds.z * 1.6; } update() { this.rotationX += this.rotSpeedX; this.rotationY += this.rotSpeedY; this.rotationZ += this.rotSpeedZ; let dx = this.targetX - this.x; let dy = this.targetY - this.y; let dz = this.targetZ - this.z; let distSq = dx * dx + dy * dy + dz * dz; if (distSq > 25) { const distance = Math.sqrt(distSq); const speed = config.moveSpeed; this.x += (dx / distance) * speed; this.y += (dy / distance) * speed; this.z += (dz / distance) * speed; } else { this.chooseNewTarget(); } const bounds = config.bounds; let bounced = false; if (Math.abs(this.x) > bounds.x) { this.x = Math.sign(this.x) * bounds.x; this.targetX = (Math.random() - 0.5) * bounds.x * 1.6; if (this.x > 0) this.targetX = -Math.abs(this.targetX); else this.targetX = Math.abs(this.targetX); bounced = true; } if (Math.abs(this.y) > bounds.y) { this.y = Math.sign(this.y) * bounds.y; this.targetY = (Math.random() - 0.5) * bounds.y * 1.6; if (this.y > 0) this.targetY = -Math.abs(this.targetY); else this.targetY = Math.abs(this.targetY); bounced = true; } if (Math.abs(this.z) > bounds.z) { this.z = Math.sign(this.z) * bounds.z; this.targetZ = (Math.random() - 0.5) * bounds.z * 1.6; if (this.z > 0) this.targetZ = -Math.abs(this.targetZ); else this.targetZ = Math.abs(this.targetZ); bounced = true; } if (bounced) { this.chooseNewTarget(); } } getProjected() { return project3D(this.x, this.y, this.z); } draw() { const proj = this.getProjected(); if (proj.scale <= 0.1 || !proj) return; if (proj.scale < 0.2 && Math.abs(this.z) > 300) return; const size = this.size * proj.scale; const rotY = this.rotationY; const rotX = this.rotationX; const cosY = Math.cos(rotY); const sinY = Math.sin(rotY); const cosX = Math.cos(rotX); const sinX = Math.sin(rotX); ctx.save(); ctx.translate(proj.x, proj.y); const corners = []; for (let i = 0; i < 8; i++) { let x = (i & 1 ? 1 : -1) * size / 2; let y = (i & 2 ? 1 : -1) * size / 2; let z = (i & 4 ? 1 : -1) * size / 2; let tx = x * cosY - z * sinY; let tz = x * sinY + z * cosY; x = tx; z = tz; let ty = y * cosX - z * sinX; tz = y * sinX + z * cosX; y = ty; z = tz; corners.push({ x, y, z }); } const faces = [ { indices: [0, 1, 3, 2], z: (corners[0].z + corners[1].z + corners[3].z + corners[2].z) / 4 }, { indices: [0, 1, 5, 4], z: (corners[0].z + corners[1].z + corners[5].z + corners[4].z) / 4 }, { indices: [1, 3, 7, 5], z: (corners[1].z + corners[3].z + corners[7].z + corners[5].z) / 4 } ]; faces.sort((a, b) => b.z - a.z); faces.forEach(face => { const alpha = 0.4 + (face.z / size) * 0.3; ctx.fillStyle = this.color.replace(')', `, ${Math.max(0.3, Math.min(0.8, alpha))})`).replace('hsl', 'hsla'); ctx.strokeStyle = this.color; ctx.lineWidth = 2.5; ctx.shadowBlur = 10; ctx.shadowColor = this.color; ctx.beginPath(); face.indices.forEach((idx, i) => { if (i === 0) ctx.moveTo(corners[idx].x, corners[idx].y); else ctx.lineTo(corners[idx].x, corners[idx].y); }); ctx.closePath(); ctx.fill(); ctx.stroke(); }); ctx.strokeStyle = this.color; ctx.lineWidth = 1.5; ctx.shadowBlur = 5; const edges = [ [0,1], [1,3], [3,2], [2,0], [4,5], [5,7], [7,6], [6,4], [0,4], [1,5], [3,7], [2,6] ]; edges.forEach(edge => { ctx.beginPath(); ctx.moveTo(corners[edge[0]].x, corners[edge[0]].y); ctx.lineTo(corners[edge[1]].x, corners[edge[1]].y); ctx.stroke(); }); ctx.restore(); ctx.shadowBlur = 0; } } class Connection { constructor(box1, box2) { this.box1 = box1; this.box2 = box2; this._distSq = null; } invalidateCache() { this._distSq = null; } draw() { const proj1 = this.box1.getProjected(); const proj2 = this.box2.getProjected(); if (!this._distSq) { const dx = this.box1.x - this.box2.x; const dy = this.box1.y - this.box2.y; const dz = this.box1.z - this.box2.z; this._distSq = dx * dx + dy * dy + dz * dz; } const maxDistSq = config.connectionDistance * config.connectionDistance; if (this._distSq > maxDistSq) return; const dist = Math.sqrt(this._distSq); const opacity = (1 - dist / config.connectionDistance) * 0.5; ctx.strokeStyle = `rgba(100, 200, 255, ${opacity})`; ctx.lineWidth = 1.5; ctx.shadowBlur = 5; ctx.shadowColor = `rgba(100, 200, 255, ${opacity * 0.5})`; ctx.beginPath(); ctx.moveTo(proj1.x, proj1.y); ctx.lineTo(proj2.x, proj2.y); ctx.stroke(); } } class ConnectionParticle { constructor(connection) { this.connection = connection; this.progress = 0; this.speed = config.particleSpeed + Math.random() * 0.01; this.size = 4 + Math.random() * 3; this.color = `hsl(${180 + Math.random() * 60}, 90%, 75%)`; this.direction = Math.random() > 0.5 ? 1 : -1; } update() { this.progress += this.speed * this.direction; if (this.progress >= 1 || this.progress <= 0) { return false; } return true; } draw() { const box1 = this.connection.box1; const box2 = this.connection.box2; const t = Math.max(0, Math.min(1, this.progress)); const x = box1.x + (box2.x - box1.x) * t; const y = box1.y + (box2.y - box1.y) * t; const z = box1.z + (box2.z - box1.z) * t; const proj = project3D(x, y, z); if (proj.scale < 0.1) return; const radius = this.size * proj.scale; const glowRadius = radius * 1.5; const coreRadius = radius * 0.4; ctx.save(); ctx.globalAlpha = 0.4; ctx.fillStyle = this.color; ctx.shadowBlur = 20; ctx.shadowColor = this.color; ctx.beginPath(); ctx.arc(proj.x, proj.y, glowRadius, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; ctx.shadowBlur = 15; ctx.beginPath(); ctx.arc(proj.x, proj.y, radius, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 0.8; ctx.fillStyle = '#ffffff'; ctx.shadowBlur = 0; ctx.beginPath(); ctx.arc(proj.x, proj.y, coreRadius, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } function resize() { const parent = canvas.parentElement; if (!parent) return; w = canvas.width = parent.offsetWidth || window.innerWidth / 2; h = canvas.height = parent.offsetHeight || window.innerHeight; } function init() { boxes = []; connections = []; particles = []; for (let i = 0; i < config.boxCount; i++) { boxes.push(new Box3D()); } setTimeout(() => { updateConnections(); if (connections.length > 0) { for (let i = 0; i < 20; i++) { const randomConn = connections[Math.floor(Math.random() * connections.length)]; particles.push(new ConnectionParticle(randomConn)); } } }, 100); } function updateConnections() { connections = []; const maxDistSq = config.connectionDistance * config.connectionDistance; for (let i = 0; i < boxes.length; i++) { for (let j = i + 1; j < boxes.length; j++) { const dx = boxes[i].x - boxes[j].x; const dy = boxes[i].y - boxes[j].y; const dz = boxes[i].z - boxes[j].z; const distSq = dx * dx + dy * dy + dz * dz; if (distSq < maxDistSq) { const conn = new Connection(boxes[i], boxes[j]); conn._distSq = distSq; connections.push(conn); } } } } function animate() { ctx.fillStyle = 'rgba(0, 0, 0, 0)'; ctx.clearRect(0, 0, w, h); time += 1; boxes.forEach(box => box.update()); if (time % 30 === 0) { updateConnections(); } connections.forEach(conn => conn.draw()); if (connections.length > 0) { for (let i = 0; i < 8; i++) { if (Math.random() < config.particleSpawnChance) { const randomConn = connections[Math.floor(Math.random() * connections.length)]; particles.push(new ConnectionParticle(randomConn)); } } } const particlesToKeep = []; for (let i = 0; i < particles.length; i++) { const particle = particles[i]; if (particle.update()) { particlesToKeep.push(particle); particle.draw(); } } particles = particlesToKeep; if (particles.length > 150) { particles = particles.slice(-150); } const sortedBoxes = [...boxes].sort((a, b) => { return b.z - a.z; }); sortedBoxes.forEach(box => box.draw()); animationId = requestAnimationFrame(animate); } function startAnimation() { try { resize(); init(); if (animationId) cancelAnimationFrame(animationId); animate(); } catch (error) { console.error('Error starting animation:', error); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startAnimation); } else { setTimeout(startAnimation, 100); } window.addEventListener('resize', () => { resize(); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init3DAnimation); } else { setTimeout(init3DAnimation, 100); }