Files

202 lines
8.4 KiB
JavaScript

const nodosData = [
// Reemplaza el src con la ruta real de tus logos
{ id: 'tl', x0: 220, y0: 140, r: 70, color: '#16a085', html: '<img src="assets/logos/excellence_b.png" alt="EXCELLENCE">' },
{ id: 'bl', x0: 200, y0: 420, r: 70, color: '#f39200', html: '<img src="assets/logos/projects_b.png" alt="PROJECTS">' },
{ id: 'tr', x0: 780, y0: 110, r: 70, color: '#c41230', html: '<img src="assets/logos/academy_b.png" alt="ACADEMY">' },
{ id: 'br', x0: 750, y0: 400, r: 70, color: '#0061a0', html: '<img src="assets/logos/digital_b.png" alt="DIGITAL">' },
{ id: 'c', x0: 500, y0: 280, r: 85, color: '#ffffff', html: '<img src="assets/logos/ide.png" alt="EL TODO">' }
];
const conexiones = [
{ id: 'arcL', from: 'tl', to: 'bl', type: 'vertical', curveOut: -180 },
{ id: 'c1', from: 'tl', to: 'c', type: 'horizontal', curveOut: 0 },
{ id: 'c2', from: 'bl', to: 'c', type: 'horizontal', curveOut: 0 },
{ id: 'arcR', from: 'tr', to: 'br', type: 'vertical', curveOut: 180 },
{ id: 'c3', from: 'tr', to: 'c', type: 'horizontal', curveOut: 0 },
{ id: 'c4', from: 'br', to: 'c', type: 'horizontal', curveOut: 0 }
];
const contenedorNodos = document.getElementById('grupo-nodos');
const grupoLineas = document.getElementById('grupo-lineas');
const svgDefs = document.getElementById('svg-defs');
const nodosMap = {};
// 1. Renderizado de Nodos HTML (AHORA FIJOS)
nodosData.forEach(nodo => {
nodosMap[nodo.id] = nodo;
const el = document.createElement('div');
el.className = 'nodo-circulo';
el.id = `node-${nodo.id}`;
el.style.backgroundColor = nodo.color; // Lo comento para que el fondo sea blanco y se vea bien tu logo, descomenta si prefieres el fondo de color
//el.style.border = `4px solid ${nodo.color}`; // Agrego un borde con el color corporativo
el.style.width = `${(nodo.r * 2 / 1000) * 100}%`;
el.style.height = `${(nodo.r * 2 / 500) * 100}%`;
// Asignamos la posición una única vez
nodo.x = nodo.x0;
nodo.y = nodo.y0;
el.style.left = `${(nodo.x / 1000) * 100}%`;
el.style.top = `${(nodo.y / 500) * 100}%`;
const content = document.createElement('div');
content.className = 'nodo-contenido';
content.innerHTML = nodo.html;
el.appendChild(content);
contenedorNodos.appendChild(el);
nodo.el = el;
});
const offsetsLineas = [-12, -6, 0, 6, 12];
// 2. Pre-calcular rutas y gradientes (UNA SOLA VEZ)
conexiones.forEach(conn => {
const n1 = nodosMap[conn.from];
const n2 = nodosMap[conn.to];
const isHoriz = conn.type === 'horizontal';
conn.rutasCache = {}; // Diccionario para guardar las rutas
// Crear y fijar Gradiente
const gradId = `grad-${conn.from}-${conn.to}`;
const grad = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
grad.setAttribute('id', gradId);
grad.setAttribute('gradientUnits', 'userSpaceOnUse');
grad.setAttribute('x1', n1.x);
grad.setAttribute('y1', n1.y);
grad.setAttribute('x2', n2.x);
grad.setAttribute('y2', n2.y);
const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
stop1.setAttribute('offset', '0%');
stop1.setAttribute('stop-color', n1.color);
const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
stop2.setAttribute('offset', '100%');
const colorTarget = n2.id === 'c' ? '#0e626e' : n2.color;
stop2.setAttribute('stop-color', colorTarget);
grad.appendChild(stop1);
grad.appendChild(stop2);
svgDefs.appendChild(grad);
// Función matemática
const calcularRuta = (offsetX, offsetY) => {
let cp1x, cp1y, cp2x, cp2y;
if (isHoriz) {
const distanciaX = n2.x - n1.x;
cp1x = n1.x + distanciaX * 0.5;
cp1y = n1.y;
cp2x = n2.x - distanciaX * 0.5;
cp2y = n2.y;
} else {
cp1x = n1.x + conn.curveOut;
cp1y = n1.y;
cp2x = n2.x + conn.curveOut;
cp2y = n2.y;
}
return `M ${n1.x + offsetX} ${n1.y + offsetY} C ${cp1x + offsetX} ${cp1y + offsetY}, ${cp2x + offsetX} ${cp2y + offsetY}, ${n2.x + offsetX} ${n2.y + offsetY}`;
};
// Dibujar y Cachear las 5 líneas base
conn.pathsBase = [];
offsetsLineas.forEach(offset => {
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.classList.add('linea-base');
path.setAttribute('stroke', `url(#${gradId})`);
const rutaCalculada = calcularRuta(isHoriz ? 0 : offset, isHoriz ? offset : 0);
path.setAttribute('d', rutaCalculada);
conn.rutasCache[offset] = rutaCalculada; // Guardado en caché
grupoLineas.appendChild(path);
conn.pathsBase.push({ el: path, offset: offset });
});
// Crear pulsos
conn.pulsos = [];
const cantPulsos = 6;
for (let i = 0; i < cantPulsos; i++) {
const pathPulso = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathPulso.classList.add('linea-pulso');
const carrilInicial = offsetsLineas[Math.floor(Math.random() * offsetsLineas.length)];
pathPulso.setAttribute('d', conn.rutasCache[carrilInicial]); // Le asignamos su ruta desde la caché
grupoLineas.appendChild(pathPulso);
conn.pulsos.push({
el: pathPulso,
currentOffset: carrilInicial,
needsPathUpdate: false, // Bandera de optimización
dash: (1200 / cantPulsos) * i + Math.random() * 50,
speed: 2.0 + Math.random() * 1.5
});
}
});
// 3. Lógica de salto entre carriles
setInterval(() => {
conexiones.forEach(conn => {
conn.pulsos.forEach(p => {
if (Math.random() > 0.8) {
p.currentOffset = offsetsLineas[Math.floor(Math.random() * offsetsLineas.length)];
p.needsPathUpdate = true; // Avisamos que en el próximo frame debe cambiar de ruta
}
});
});
}, 1200);
// 4. Motor de Animación Ultraligero
let animando = false;
let rafId = null;
function animarFrame() {
if (!animando) return; // corta el loop cuando no es visible
conexiones.forEach(conn => {
conn.pulsos.forEach(p => {
p.dash -= p.speed;
if (p.needsPathUpdate) {
p.el.setAttribute('d', conn.rutasCache[p.currentOffset]);
p.needsPathUpdate = false;
}
p.el.style.strokeDashoffset = p.dash;
});
});
rafId = requestAnimationFrame(animarFrame);
}
function iniciarAnimacion() {
if (animando) return;
animando = true;
rafId = requestAnimationFrame(animarFrame);
}
function detenerAnimacion() {
animando = false;
if (rafId) cancelAnimationFrame(rafId);
}
// Detectar visibilidad de la sección del header
const heroVisual = document.querySelector('.hero-visual');
if (heroVisual && 'IntersectionObserver' in window) {
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) iniciarAnimacion();
else detenerAnimacion();
});
}, { threshold: 0.05 });
io.observe(heroVisual);
} else {
iniciarAnimacion(); // fallback
}
// Pausar también cuando la pestaña queda oculta
document.addEventListener('visibilitychange', () => {
if (document.hidden) detenerAnimacion();
else if (heroVisual && heroVisual.getBoundingClientRect().top < window.innerHeight) {
iniciarAnimacion();
}
});