diff --git a/assets/img/academy.png b/assets/img/academy.png new file mode 100644 index 0000000..f7fcd35 Binary files /dev/null and b/assets/img/academy.png differ diff --git a/assets/img/digital.png b/assets/img/digital.png new file mode 100644 index 0000000..bdbe995 Binary files /dev/null and b/assets/img/digital.png differ diff --git a/assets/img/excellence.png b/assets/img/excellence.png new file mode 100644 index 0000000..28f14ac Binary files /dev/null and b/assets/img/excellence.png differ diff --git a/assets/img/projects.png b/assets/img/projects.png new file mode 100644 index 0000000..8154049 Binary files /dev/null and b/assets/img/projects.png differ diff --git a/assets/video/Video header Ide.mp4 b/assets/video/Video header Ide.mp4 index 9226939..a7c598d 100644 Binary files a/assets/video/Video header Ide.mp4 and b/assets/video/Video header Ide.mp4 differ diff --git a/assets/video/Video header Ide3.mp4 b/assets/video/Video header Ide3.mp4 index 4d87212..3ec3aa7 100644 Binary files a/assets/video/Video header Ide3.mp4 and b/assets/video/Video header Ide3.mp4 differ diff --git a/css/styles.css b/css/styles.css index 33d3b43..2a36548 100644 --- a/css/styles.css +++ b/css/styles.css @@ -23,6 +23,7 @@ --ide-digital: #2c69ab; --teal-dark: #1B5E6E; --cta-hover:#40ab8e; + --bg-subtle: #ecf9fd; /* Tema Claro (Por defecto) */ --bg-body: #F8FAFB; @@ -46,6 +47,9 @@ --shadow-sm: 0 1px 3px rgba(10, 72, 94, 0.05); --shadow-md: 0 4px 16px rgba(10, 72, 94, 0.08); --shadow-lg: 0 10px 30px rgba(10, 72, 94, 0.12); + --shadow-canva: rgba(10, 72, 94, 0.05); + + --ide-primary-line: rgba(9,75,99, 0.4); --radius-sm: 6px; --radius-md: 12px; @@ -63,6 +67,7 @@ --bg-surface: #18222A; --bg-surface-elevated: #202D37; --bg-subtle: #24333E; + --ide-primary-dark: #1D4672; --bg-card: #18222A; --ripple-color:#253441; --bg-card-alt: var(--bg-muted); @@ -79,6 +84,7 @@ --ide-digital: #2c69ab; --teal-dark: #40ab8e; --cta-hover:#0d5a91; + --bg-subtle: #03151c;; --text-primary:#4fb389; @@ -87,9 +93,12 @@ --text-muted: #8E9FAA; --text-inverse: #0E2833; + --ide-primary-line: rgba(79,179,137, 0.4); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.4); --shadow-lg: 0 10px 35px rgba(0, 0, 0, 0.55); + --shadow-canva: rgba(92, 103, 107, 0.432); } /* --- 2. Reseteo y Base --- */ @@ -189,7 +198,9 @@ p, li, .lead-text, .text-body, .text-muted { font-family: 'Plus Jakarta Sans', sans-serif; } - +.bg-subtle-section{ + background-color: var(--bg-subtle); +} .lead-text { font-size: 1.15rem; color: var(--text-muted); @@ -580,6 +591,20 @@ p, li, .lead-text, .text-body, .text-muted { color: var(--ide-accent); background-color: var(--ide-accent-soft); } +.btn-primary-cta { + background-color: var(--ide-primary-dark); + color: #FFFFFF; + border-color: var(--ide-primary); +} + +.btn-primary-cta:hover { + background-color: var(--ide-primary-dark); + border-color: var(--ide-primary-dark); + color: #FFFFFF; + box-shadow: var(--shadow-md); + transform: translateY(-1px); +} + .btn-outline-cta { background-color: transparent; color: var(--text-inverse); @@ -1437,7 +1462,101 @@ p, li, .lead-text, .text-body, .text-muted { border-color: var(--ide-primary); color: #FFFFFF; } +/* --- Tarjetas de Ecosistema --- */ +.card-excellence:hover { + border-color: var(--ide-excellence); + box-shadow: 0 0 30px rgba(79,179,137, 0.5); + transform: translateY(-2px); +} +.card-projects:hover { + border-color: var(--ide-projects); + box-shadow: 0 0 30px rgba(237,141,1, 0.5); + transform: translateY(-2px); +} +.card-academy:hover { + border-color: var(--ide-academy); + box-shadow: 0 0 30px rgba(168,60,65, 0.5); + transform: translateY(-2px); +} +.card-digital:hover { + border-color: var(--ide-digital); + box-shadow: 0 0 30px rgba(44,105,171, 0.5); + transform: translateY(-2px); +} +/* --- Formato NetSchema --- */ +.netschema-section { + width: 100%; + padding: 40px 20px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--bg-subtle); +} + +/* Envoltura con aspecto horizontal responsivo */ +.netschema-wrapper { + position: relative; + width: 100%; + max-width: 1240px; + overflow: hidden; +} +.netschema-header { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + margin-bottom: 1.5rem; +} +/* El canvas se escala proporcionalmente según el ancho del contenedor */ +canvas { + display: block; + width: 100%; + height: auto; + aspect-ratio: 1240 / 520; + cursor: default; +} + +/* ========================================================================== + LEYENDA / TOOLTIP FLOTANTE + ========================================================================== */ +.network-tooltip { + position: absolute; + pointer-events: none; + background: rgba(22, 24, 38, 0.94); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + color: #ffffff; + padding: 10px 15px; + border-radius: 8px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22); + transform: translate(-50%, -125%); + transition: opacity 0.2s ease, transform 0.2s ease; + z-index: 20; + max-width: 240px; + text-align: center; +} + +.network-tooltip.hidden { + opacity: 0; + visibility: hidden; + transform: translate(-50%, -115%); +} + +.tooltip-title { + font-weight: 700; + font-size: 14px; + margin-bottom: 3px; + color: #ffffff; + letter-spacing: 0.3px; +} + +.tooltip-desc { + font-size: 12px; + color: #cfd4dc; + line-height: 1.35; +} /* --- 17. Footer --- */ #site-footer { position: relative; diff --git a/desafios.html b/desafios.html index 809e74c..cf5cfb2 100644 --- a/desafios.html +++ b/desafios.html @@ -49,9 +49,9 @@
- PUNTO DE PARTIDA -

¿Qué está pasando en tu organización?

-

+ PUNTO DE PARTIDA +

¿Qué está pasando en tu organización?

+

No necesitás tener la solución definida para empezar a conversar. Si reconocés alguno de estos síntomas o metas en tu contexto, hacé clic para explorar cómo podemos abordarlo.

diff --git a/ecosistema.html b/ecosistema.html index 1a0a424..5001f30 100644 --- a/ecosistema.html +++ b/ecosistema.html @@ -7,7 +7,8 @@ - + + @@ -17,66 +18,85 @@
-
-
- LA PROPUESTA DE VALOR -

Una mirada.
Múltiples capacidades.
Un mismo propósito.

-

+

+ + + + + +
+ + +
+
+ LA PROPUESTA DE VALOR +

Una mirada.
Múltiples capacidades.
Un mismo propósito.

+

No se trata de sumar soluciones aisladas. Se trata de conectar las capacidades necesarias para generar una transformación real que perdure en el conjunto de la organización.

-
+

“No todos los desafíos necesitan todo el Ecosistema. Pero todos pueden beneficiarse de una mirada capaz de comprender el conjunto.”

+
- -
-
-
+ +
+
+
ARQUITECTURA DE INTEGRACIÓN -

El Modelo de Red Sistémica

- -
-
- - EXCELLENCE -

Procesos & Calidad

-
-
- - PROJECT -

Dirección & PMO

-
-
- - ACADEMY -

Capacidades & Personas

-
-
- - DIGITAL -

IA & Automatización

-
-
+

El Modelo de Red Sistémica

+ + + + + +
- +
+ MÓDULOS +

Conocé el detalle de Nuestro Ecosistema

+
-
+
GESTIÓN Y PROCESOS -

IDE Excellence

Para fortalecer la gestión: Diseñamos y optimizamos la arquitectura de procesos organizacionales, sistemas de gestión de calidad y cumplimiento normativo internacional.

@@ -88,26 +108,19 @@
-
- -
Espacio Gráfico IDE Excellence
-
+ IDE Excellence
-
+
-
- -
Espacio Gráfico IDE Project
-
+ IDE Project
DIRECCIÓN Y RESULTADOS -

IDE Project

Para convertir desafíos en proyectos y proyectos en resultados: Estructuramos la metodología y el control para que las iniciativas estratégicas se cumplan en tiempo, costo y alcance.

@@ -122,13 +135,12 @@
-
+
DESARROLLO HUMANO -

IDE Academy

- Para desarrollar las capacidades que hacen posible la transformación: Formación ejecutiva e In-Company diseñada para que los equipos comprendan, apliquen y sostengan los cambios. + Para desarrollar las capacidades que hacen posible la transformación:Formación ejecutiva e In-Company diseñada para que los equipos comprendan, apliquen y sostengan los cambios.

-
- -
Espacio Gráfico IDE Academy
-
+ IDE Academy
-
+
-
- -
Espacio Gráfico IDE Digital
-
+ IDE Digital
TECNOLOGÍA CON SENTIDO -

IDE Digital

Para integrar tecnología, datos e IA con propósito: No sumamos herramientas por moda; automatizamos procesos clave y potenciamos la toma de decisiones con analítica de datos.

@@ -174,6 +179,8 @@
+ +
@@ -213,22 +220,25 @@
-
-
-

¿Qué combinación de capacidades necesita tu organización?

-

+

+
+

¿Qué combinación de capacidades necesita tu organización?

+

Conversemos para diseñar una propuesta a medida de tu realidad.

+ +
+ diff --git a/ide-academy.html b/ide-academy.html index becba4d..ac994fe 100644 --- a/ide-academy.html +++ b/ide-academy.html @@ -7,7 +7,8 @@ - + + diff --git a/insights.html b/insights.html index a08b52a..207e906 100644 --- a/insights.html +++ b/insights.html @@ -7,7 +7,8 @@ - + + diff --git a/js/netschema.js b/js/netschema.js new file mode 100644 index 0000000..1b5d05b --- /dev/null +++ b/js/netschema.js @@ -0,0 +1,543 @@ +// Mapa: propiedad de CONFIG -> nombre de variable CSS +const CSS_VARS = { + greenNodesColor: "--ide-primary", + greenStrokeColor: "--ide-primary-light", + linkLineColor: "--ide-primary-line", + linkGreenColor: "--ide-primary-line", + linkActiveColor: "--ide-primary-light", + purpleCoreLineColor: "--ide-primary-line", + shadowCanva: "--shadow-canva", + textMain: "--text-main", + textInverse: "--text-inverse" +}; + +// Mapa: id de nodo violeta -> variable CSS +const PURPLE_NODE_VARS = { + p_top: "--ide-excellence", + p_bottom: "--ide-digital", + p_left: "--ide-academy", + p_right: "--ide-projects" +}; + +// Variable CSS común para todos los amarillos +const YELLOW_NODE_VAR = "--ide-primary-dark"; + +/* ========================================================================== + UTILIDAD: LEER VARIABLES CSS + ========================================================================== */ + +function readCSSVariable(variableName, fallback = "") { + const value = getComputedStyle(document.documentElement) + .getPropertyValue(variableName) + .trim(); + return value || fallback; +} + +/* ========================================================================== + CONFIGURACIÓN GENERAL + ========================================================================== */ + +const CONFIG = { + // 1. Color global configurable para TODOS los nodos verdes + greenNodesColor: readCSSVariable(CSS_VARS.greenNodesColor), + greenStrokeColor: readCSSVariable(CSS_VARS.greenStrokeColor), + + // 2. Estilos de líneas punteadas y dinámicas + linkLineColor: readCSSVariable(CSS_VARS.linkLineColor), + linkGreenColor: readCSSVariable(CSS_VARS.linkGreenColor), + linkActiveColor: readCSSVariable(CSS_VARS.linkActiveColor), + purpleCoreLineColor: readCSSVariable(CSS_VARS.purpleCoreLineColor), + shadowCanva: readCSSVariable(CSS_VARS.shadowCanva), + textMain: readCSSVariable(CSS_VARS.textMain), + textInverse: readCSSVariable(CSS_VARS.textInverse), + + // 3. Nodos VIOLETAS centrales (posiciones, color individual, leyenda) + purpleNodes: [ + { + id: "p_top", + x: 615, y: 185, + radius: 42, + label: "Excellence", + color: readCSSVariable(PURPLE_NODE_VARS.p_top), + desc: "Servidor central de control y despacho de tareas." + }, + { + id: "p_bottom", + x: 615, y: 335, + radius: 42, + label: "Digital", + color: readCSSVariable(PURPLE_NODE_VARS.p_bottom), + desc: "Base de datos y redundancia estructural sur." + }, + { + id: "p_left", + x: 485, y: 260, + radius: 42, + label: "Academy", + color: readCSSVariable(PURPLE_NODE_VARS.p_left), + desc: "Pasarela occidental hacia submódulos de entrada." + }, + { + id: "p_right", + x: 745, y: 260, + radius: 42, + label: "Projects", + color: readCSSVariable(PURPLE_NODE_VARS.p_right), + desc: "Concentrador oriental hacia subredes de distribución." + } + ], + + // 4. Nodos AMARILLOS: Cada uno conectado a 2 VIOLETAS + yellowNodes: [ + // --- Lado Izquierdo --- + { id: "yl_1", x: 420, y: 130, r: 26, label: "5S", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Capacitación e Impmentación", purpleTargets: ["p_top", "p_left"], side: "left" }, + { id: "yl_2", x: 350, y: 165, r: 24, label: "ISO 9001", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Certificación", purpleTargets: ["p_top", "p_left"], side: "left" }, + { id: "yl_3", x: 355, y: 245, r: 25, label: "KAIZEN", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Modificación de Procesos", purpleTargets: ["p_left", "p_top"], side: "left" }, + { id: "yl_4", x: 335, y: 335, r: 26, label: "BPM", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Automatización de Procesos de Venta", purpleTargets: ["p_left", "p_bottom"], side: "left" }, + { id: "yl_5", x: 395, y: 415, r: 28, label: "KPI", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Implementación y Monitoreo", purpleTargets: ["p_bottom", "p_left"], side: "left" }, + { id: "yl_6", x: 255, y: 350, r: 25, label: "PDCA", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Capacitación y Mejora de Taller", purpleTargets: ["p_left", "p_bottom"], side: "left" }, + + // --- Lado Derecho --- + { id: "yr_1", x: 800, y: 135, r: 25, label: "BPR", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Capacitación e Impmentación", purpleTargets: ["p_top", "p_right"], side: "right" }, + { id: "yr_2", x: 875, y: 155, r: 25, label: "ISO45001", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Certificación", purpleTargets: ["p_top", "p_right"], side: "right" }, + { id: "yr_3", x: 950, y: 195, r: 25, label: "LEAN", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Modificación de Procesos", purpleTargets: ["p_right", "p_top"], side: "right" }, + { id: "yr_4", x: 875, y: 235, r: 25, label: "ISO14001", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Automatización de Procesos de Venta", purpleTargets: ["p_right", "p_top"], side: "right" }, + { id: "yr_5", x: 890, y: 300, r: 25, label: "PMO", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Mejoras en procedimientos de Atención de situaciones críticas", purpleTargets: ["p_right", "p_bottom"], side: "right" }, + { id: "yr_6", x: 965, y: 345, r: 25, label: "SIGMA", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Capacitación y Mejora de Taller", purpleTargets: ["p_right", "p_bottom"], side: "right" }, + { id: "yr_7", x: 890, y: 375, r: 25, label: "IA", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Automatización de Toma de Decisiones", purpleTargets: ["p_bottom", "p_right"], side: "right" }, + { id: "yr_8", x: 830, y: 420, r: 26, label: "RPA", color: readCSSVariable(YELLOW_NODE_VAR, "#24333E"), desc: "Implementación y Monitoreo", purpleTargets: ["p_bottom", "p_right"], side: "right" } + ] +}; + +/* ========================================================================== + MOTOR INTERACTIVO Y RENDERIZADO + ========================================================================== */ + +const canvas = document.getElementById("networkCanvas"); +const ctx = canvas.getContext("2d"); +const tooltip = document.getElementById("tooltip"); +const tooltipTitle = document.getElementById("tooltip-title"); +const tooltipDesc = document.getElementById("tooltip-desc"); + +let nodes = []; +let links = []; +let hoveredNode = null; +let dashOffset = 0; + +// Construcción de la estructura +function initNetwork() { + nodes = []; + links = []; + const linkSet = new Set(); // Previene enlaces duplicados entre mismos nodos + + function addLink(fromId, toId, type, curved = false, curvature = 0) { + const keyA = `${fromId}->${toId}`; + const keyB = `${toId}->${fromId}`; + if (linkSet.has(keyA) || linkSet.has(keyB)) return; + linkSet.add(keyA); + links.push({ from: fromId, to: toId, type, curved, curvature }); + } + + // 1. Agregar Nodos Violetas + CONFIG.purpleNodes.forEach(p => { + nodes.push({ + ...p, + type: "purple", + baseX: p.x, + baseY: p.y, + speedOffset: Math.random() * 10 + }); + }); + + // Conectar los violetas entre sí (malla central) + const purples = nodes.filter(n => n.type === "purple"); + for (let i = 0; i < purples.length; i++) { + for (let j = i + 1; j < purples.length; j++) { + addLink(purples[i].id, purples[j].id, "purple-purple", false); + } + } + + // 2. Agregar Nodos Amarillos y crear DOBLE conexión curva hacia los violetas + CONFIG.yellowNodes.forEach((y) => { + nodes.push({ + id: y.id, + x: y.x, + y: y.y, + baseX: y.x, + baseY: y.y, + radius: y.r, + label: y.label, + color: y.color, + desc: y.desc, + side: y.side, + type: "yellow", + speedOffset: Math.random() * 10 + }); + + if (Array.isArray(y.purpleTargets)) { + y.purpleTargets.forEach((targetPurpleId, index) => { + addLink( + targetPurpleId, + y.id, + "purple-yellow", + true, + index === 0 ? 0.16 : -0.16 + ); + }); + } + }); + + // 3. Crear Nodos Verdes Terciarios + const greenPositions = [ + // Lado Izquierdo + { id: "gl_1", x: 190, y: 95, side: "left" }, + { id: "gl_2", x: 260, y: 120, side: "left" }, + { id: "gl_3", x: 140, y: 155, side: "left" }, + { id: "gl_4", x: 75, y: 150, side: "left" }, + { id: "gl_5", x: 130, y: 225, side: "left" }, + { id: "gl_6", x: 65, y: 230, side: "left" }, + { id: "gl_7", x: 195, y: 235, side: "left" }, + { id: "gl_8", x: 60, y: 290, side: "left" }, + { id: "gl_9", x: 165, y: 305, side: "left" }, + { id: "gl_10", x: 75, y: 375, side: "left" }, + { id: "gl_11", x: 140, y: 380, side: "left" }, + { id: "gl_12", x: 145, y: 440, side: "left" }, + { id: "gl_13", x: 235, y: 425, side: "left" }, + + // Lado Derecho + { id: "gr_1", x: 1040, y: 90, side: "right" }, + { id: "gr_2", x: 960, y: 110, side: "right" }, + { id: "gr_3", x: 1025, y: 150, side: "right" }, + { id: "gr_4", x: 1095, y: 155, side: "right" }, + { id: "gr_5", x: 1160, y: 150, side: "right" }, + { id: "gr_6", x: 1030, y: 220, side: "right" }, + { id: "gr_7", x: 1105, y: 215, side: "right" }, + { id: "gr_8", x: 1170, y: 225, side: "right" }, + { id: "gr_9", x: 1060, y: 285, side: "right" }, + { id: "gr_10", x: 1120, y: 305, side: "right" }, + { id: "gr_11", x: 1175, y: 290, side: "right" }, + { id: "gr_12", x: 1025, y: 335, side: "right" }, + { id: "gr_13", x: 1095, y: 350, side: "right" }, + { id: "gr_14", x: 1035, y: 400, side: "right" }, + { id: "gr_15", x: 1085, y: 435, side: "right" }, + { id: "gr_16", x: 1165, y: 385, side: "right" }, + { id: "gr_17", x: 970, y: 425, side: "right" } + ]; + + greenPositions.forEach(g => { + nodes.push({ + id: g.id, + x: g.x, + y: g.y, + baseX: g.x, + baseY: g.y, + radius: 14, + type: "green", + side: g.side, + speedOffset: Math.random() * 10 + }); + }); + + // 4. CONEXIÓN AMARILLOS <-> VERDES (cobertura total en ambas direcciones) + const yellows = nodes.filter(n => n.type === "yellow"); + const greens = nodes.filter(n => n.type === "green"); + + // PASO A: cada amarillo con al menos 1 verde + yellows.forEach((yNode, idx) => { + const candidateGreens = greens.filter(g => g.side === yNode.side); + candidateGreens.sort((a, b) => { + const distA = Math.hypot(a.baseX - yNode.baseX, a.baseY - yNode.baseY); + const distB = Math.hypot(b.baseX - yNode.baseX, b.baseY - yNode.baseY); + return distA - distB; + }); + + if (candidateGreens.length > 0) { + addLink( + yNode.id, + candidateGreens[0].id, + "yellow-green", + true, + idx % 2 === 0 ? 0.20 : -0.20 + ); + } + }); + + // PASO B: cada verde con al menos 1 amarillo + greens.forEach((gNode, idx) => { + const alreadyConnected = links.some( + l => l.type === "yellow-green" && (l.from === gNode.id || l.to === gNode.id) + ); + + const candidateYellows = yellows.filter(y => y.side === gNode.side); + candidateYellows.sort((a, b) => { + const distA = Math.hypot(a.baseX - gNode.baseX, a.baseY - gNode.baseY); + const distB = Math.hypot(b.baseX - gNode.baseX, b.baseY - gNode.baseY); + return distA - distB; + }); + + if (!alreadyConnected && candidateYellows.length > 0) { + addLink( + candidateYellows[0].id, + gNode.id, + "yellow-green", + true, + idx % 2 === 0 ? -0.22 : 0.22 + ); + } else if (candidateYellows.length > 1 && idx % 3 === 0) { + addLink( + candidateYellows[1].id, + gNode.id, + "yellow-green", + true, + idx % 2 === 0 ? 0.26 : -0.26 + ); + } + }); +} + +// Bucle de actualización (flotación en zona segura) +function update(time) { + const maxOffset = 4.5; + + nodes.forEach(node => { + if (hoveredNode && hoveredNode.id === node.id) { + node.x = node.baseX; + node.y = node.baseY; + return; + } + + const t = (time * 0.0016) + node.speedOffset; + node.x = node.baseX + Math.sin(t) * maxOffset; + node.y = node.baseY + Math.cos(t * 0.85) * (maxOffset * 0.75); + }); + + dashOffset -= 0.35; +} + +// Renderizado de líneas curvadas +function drawConnection(source, target, link, isHighlight) { + ctx.save(); + ctx.beginPath(); + ctx.moveTo(source.x, source.y); + + if (link.curved) { + const midX = (source.x + target.x) / 2; + const midY = (source.y + target.y) / 2; + const dx = target.x - source.x; + const dy = target.y - source.y; + const dist = Math.hypot(dx, dy); + + const nx = -dy / dist; + const ny = dx / dist; + + const arcHeight = dist * link.curvature; + const cpX = midX + nx * arcHeight; + const cpY = midY + ny * arcHeight; + + ctx.quadraticCurveTo(cpX, cpY, target.x, target.y); + } else { + ctx.lineTo(target.x, target.y); + } + + if (link.type === "purple-purple") { + ctx.setLineDash([4, 4]); + ctx.lineDashOffset = dashOffset * 0.5; + ctx.strokeStyle = isHighlight ? CONFIG.linkActiveColor : CONFIG.purpleCoreLineColor; + ctx.lineWidth = isHighlight ? 2.6 : 1.8; + } else if (link.type === "yellow-green") { + ctx.setLineDash([3, 4]); + ctx.lineDashOffset = dashOffset; + ctx.strokeStyle = isHighlight ? CONFIG.linkActiveColor : CONFIG.linkGreenColor; + ctx.lineWidth = isHighlight ? 2.0 : 0.95; + } else { + ctx.setLineDash([3, 4]); + ctx.lineDashOffset = dashOffset; + ctx.strokeStyle = isHighlight ? CONFIG.linkActiveColor : CONFIG.linkLineColor; + ctx.lineWidth = isHighlight ? 2.0 : 1.05; + } + + ctx.stroke(); + ctx.restore(); +} + +// Renderizado del Canvas +function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + const nodeMap = new Map(nodes.map(n => [n.id, n])); + + // 1. Dibujar líneas + links.forEach(link => { + const s = nodeMap.get(link.from); + const t = nodeMap.get(link.to); + if (!s || !t) return; + + const isHighlight = hoveredNode && (hoveredNode.id === s.id || hoveredNode.id === t.id); + drawConnection(s, t, link, isHighlight); + }); + + // 2. Dibujar nodos + nodes.forEach(node => { + ctx.save(); + ctx.beginPath(); + ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2); + + if (node.type === "purple") { + ctx.fillStyle = node.color; + ctx.strokeStyle = node.color; + ctx.lineWidth = 1.8; + } else if (node.type === "yellow") { + ctx.fillStyle = node.color; + ctx.strokeStyle = node.color; + ctx.lineWidth = 1.3; + } else { + ctx.fillStyle = CONFIG.greenNodesColor; + ctx.strokeStyle = CONFIG.greenStrokeColor; + ctx.lineWidth = 1.1; + } + + if (hoveredNode && hoveredNode.id === node.id) { + ctx.shadowColor = CONFIG.shadowCanva; + ctx.shadowBlur = 14; + } + + ctx.fill(); + ctx.stroke(); + ctx.restore(); + + // 3. Texto en nodos violetas y amarillos + if (node.label) { + ctx.save(); + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + + if (node.type === "purple") { + ctx.fillStyle = "#ffffff"; + ctx.font = "bold 12.5px system-ui, sans-serif"; + } else if (node.type === "yellow") { + ctx.fillStyle = "#ffffff"; + ctx.font = "600 10.5px system-ui, sans-serif"; + } + + ctx.fillText(node.label, node.x, node.y); + ctx.restore(); + } + }); +} + +// Loop 60 FPS +function animate(time) { + update(time); + draw(); + requestAnimationFrame(animate); +} + +// Coordenadas con soporte responsivo +function getCanvasCoordinates(e) { + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + + return { + x: (e.clientX - rect.left) * scaleX, + y: (e.clientY - rect.top) * scaleY, + cssX: e.clientX - rect.left, + cssY: e.clientY - rect.top + }; +} + +// Eventos de Mouse +canvas.addEventListener("mousemove", (e) => { + const { x, y, cssX, cssY } = getCanvasCoordinates(e); + + const targets = nodes.filter(n => n.type === "purple" || n.type === "yellow"); + let found = null; + + for (let node of targets) { + if (Math.hypot(node.x - x, node.y - y) <= node.radius) { + found = node; + break; + } + } + + hoveredNode = found; + + if (hoveredNode) { + canvas.style.cursor = "pointer"; + tooltipTitle.textContent = hoveredNode.label; + tooltipDesc.textContent = hoveredNode.desc; + + tooltip.style.left = `${cssX}px`; + tooltip.style.top = `${cssY - (hoveredNode.radius * (canvas.getBoundingClientRect().height / canvas.height))}px`; + tooltip.classList.remove("hidden"); + } else { + canvas.style.cursor = "default"; + tooltip.classList.add("hidden"); + } +}); + +canvas.addEventListener("mouseleave", () => { + hoveredNode = null; + tooltip.classList.add("hidden"); +}); + +/* ========================================================================== + SOPORTE DE TEMA CLARO / OSCURO + -------------------------------------------------------------------------- + - Re-lee las variables CSS y actualiza CONFIG + nodos vivos. + - Observa cambios de `data-theme` o `class` en y . + ========================================================================== */ + +function refreshColorsFromCSS() { + // 1. Colores globales + CONFIG.greenNodesColor = readCSSVariable(CSS_VARS.greenNodesColor, CONFIG.greenNodesColor); + CONFIG.greenStrokeColor = readCSSVariable(CSS_VARS.greenStrokeColor, CONFIG.greenStrokeColor); + CONFIG.linkLineColor = readCSSVariable(CSS_VARS.linkLineColor, CONFIG.linkLineColor); + CONFIG.linkGreenColor = readCSSVariable(CSS_VARS.linkGreenColor, CONFIG.linkGreenColor); + CONFIG.linkActiveColor = readCSSVariable(CSS_VARS.linkActiveColor, CONFIG.linkActiveColor); + CONFIG.purpleCoreLineColor = readCSSVariable(CSS_VARS.purpleCoreLineColor, CONFIG.purpleCoreLineColor); + + // 2. Colores de violetas en CONFIG + CONFIG.purpleNodes.forEach(p => { + const cssVar = PURPLE_NODE_VARS[p.id]; + if (cssVar) p.color = readCSSVariable(cssVar, p.color); + }); + + // 3. Colores de amarillos en CONFIG + CONFIG.yellowNodes.forEach(y => { + y.color = readCSSVariable(YELLOW_NODE_VAR, y.color); + }); + + // 4. Propagar a los nodos ya instanciados + nodes.forEach(node => { + if (node.type === "purple" && PURPLE_NODE_VARS[node.id]) { + node.color = readCSSVariable(PURPLE_NODE_VARS[node.id], node.color); + } else if (node.type === "yellow") { + node.color = readCSSVariable(YELLOW_NODE_VAR, node.color); + } + // Los verdes leen CONFIG.greenNodesColor directo en draw(), no requieren update propio + }); +} + +function watchThemeChanges() { + const observer = new MutationObserver(() => { + refreshColorsFromCSS(); + }); + + // Observa (habitual en data-theme) y (por si acaso) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme", "class"] + }); + + if (document.body) { + observer.observe(document.body, { + attributes: true, + attributeFilter: ["data-theme", "class"] + }); + } +} + +// Inicialización +initNetwork(); +watchThemeChanges(); +requestAnimationFrame(animate); \ No newline at end of file diff --git a/js/netschema2.js b/js/netschema2.js new file mode 100644 index 0000000..f2958ef --- /dev/null +++ b/js/netschema2.js @@ -0,0 +1,537 @@ +// Mapa: propiedad de CONFIG -> nombre de variable CSS +const CSS_VARS = { + greenNodesColor: "--ide-primary", + greenStrokeColor: "--ide-primary-light", + linkLineColor: "--canvas-link-line-color", + linkGreenColor: "--canvas-link-green-color", + linkActiveColor: "--canvas-link-active-color", + purpleCoreLineColor: "--canvas-purple-core-line-color" +}; + +// Mapa: id de nodo violeta -> variable CSS +const PURPLE_NODE_VARS = { + p_top: "--canvas-purple-p-top-color", + p_bottom: "--canvas-purple-p-bottom-color", + p_left: "--canvas-purple-p-left-color", + p_right: "--canvas-purple-p-right-color" +}; + +// Variable CSS común para todos los amarillos +const YELLOW_NODE_VAR = "--canvas-yellow-node-color"; + +/* ========================================================================== + UTILIDAD: LEER VARIABLES CSS + ========================================================================== */ + +function readCSSVariable(variableName, fallback = "") { + const value = getComputedStyle(document.documentElement) + .getPropertyValue(variableName) + .trim(); + return value || fallback; +} + +/* ========================================================================== + CONFIGURACIÓN GENERAL + ========================================================================== */ + +const CONFIG = { + // 1. Color global configurable para TODOS los nodos verdes + greenNodesColor: readCSSVariable(CSS_VARS.greenNodesColor, "#9b1717"), + greenStrokeColor: readCSSVariable(CSS_VARS.greenStrokeColor, "#630b5f"), + + // 2. Estilos de líneas punteadas y dinámicas + linkLineColor: readCSSVariable(CSS_VARS.linkLineColor, "rgba(95, 110, 240, 0.40)"), + linkGreenColor: readCSSVariable(CSS_VARS.linkGreenColor, "rgba(235, 60, 60, 0.42)"), + linkActiveColor: readCSSVariable(CSS_VARS.linkActiveColor, "rgba(25, 35, 205, 0.95)"), + purpleCoreLineColor: readCSSVariable(CSS_VARS.purpleCoreLineColor, "rgba(35, 35, 35, 0.70)"), + + // 3. Nodos VIOLETAS centrales (posiciones, color individual, leyenda) + purpleNodes: [ + { + id: "p_top", + x: 615, y: 185, + radius: 42, + label: "Núcleo N", + color: readCSSVariable(PURPLE_NODE_VARS.p_top, "#8a00c2"), + desc: "Servidor central de control y despacho de tareas." + }, + { + id: "p_bottom", + x: 615, y: 335, + radius: 42, + label: "Núcleo S", + color: readCSSVariable(PURPLE_NODE_VARS.p_bottom, "#9900d8"), + desc: "Base de datos y redundancia estructural sur." + }, + { + id: "p_left", + x: 485, y: 260, + radius: 42, + label: "Núcleo O", + color: readCSSVariable(PURPLE_NODE_VARS.p_left, "#7e00b3"), + desc: "Pasarela occidental hacia submódulos de entrada." + }, + { + id: "p_right", + x: 745, y: 260, + radius: 42, + label: "Núcleo E", + color: readCSSVariable(PURPLE_NODE_VARS.p_right, "#9400cf"), + desc: "Concentrador oriental hacia subredes de distribución." + } + ], + + // 4. Nodos AMARILLOS: Cada uno conectado a 2 VIOLETAS + yellowNodes: [ + // --- Lado Izquierdo --- + { id: "yl_1", x: 420, y: 130, r: 26, label: "IZQ-1", color: readCSSVariable(YELLOW_NODE_VAR, "#ffff00"), desc: "Módulo intermedio A-1", purpleTargets: ["p_top", "p_left"], side: "left" }, + { id: "yl_2", x: 350, y: 165, r: 24, label: "IZQ-2", color: readCSSVariable(YELLOW_NODE_VAR, "#fff01f"), desc: "Módulo intermedio A-2", purpleTargets: ["p_top", "p_left"], side: "left" }, + { id: "yl_3", x: 355, y: 245, r: 25, label: "IZQ-3", color: readCSSVariable(YELLOW_NODE_VAR, "#ffee00"), desc: "Balanceador izquierdo", purpleTargets: ["p_left", "p_top"], side: "left" }, + { id: "yl_4", x: 335, y: 335, r: 26, label: "IZQ-4", color: readCSSVariable(YELLOW_NODE_VAR, "#ffff00"), desc: "Filtro de tráfico A-4", purpleTargets: ["p_left", "p_bottom"], side: "left" }, + { id: "yl_5", x: 395, y: 415, r: 28, label: "IZQ-5", color: readCSSVariable(YELLOW_NODE_VAR, "#fff33b"), desc: "Procesador de paquetes", purpleTargets: ["p_bottom", "p_left"], side: "left" }, + { id: "yl_6", x: 255, y: 350, r: 25, label: "IZQ-6", color: readCSSVariable(YELLOW_NODE_VAR, "#ffee00"), desc: "Enrutador secundario", purpleTargets: ["p_left", "p_bottom"], side: "left" }, + + // --- Lado Derecho --- + { id: "yr_1", x: 800, y: 135, r: 25, label: "DER-1", color: readCSSVariable(YELLOW_NODE_VAR, "#ffff00"), desc: "Concentrador de salida 1", purpleTargets: ["p_top", "p_right"], side: "right" }, + { id: "yr_2", x: 875, y: 155, r: 25, label: "DER-2", color: readCSSVariable(YELLOW_NODE_VAR, "#ffee00"), desc: "Concentrador de salida 2", purpleTargets: ["p_top", "p_right"], side: "right" }, + { id: "yr_3", x: 950, y: 195, r: 25, label: "DER-3", color: readCSSVariable(YELLOW_NODE_VAR, "#ffff00"), desc: "Distribuidor rápido 3", purpleTargets: ["p_right", "p_top"], side: "right" }, + { id: "yr_4", x: 875, y: 235, r: 25, label: "DER-4", color: readCSSVariable(YELLOW_NODE_VAR, "#fff33b"), desc: "Replicador este 4", purpleTargets: ["p_right", "p_top"], side: "right" }, + { id: "yr_5", x: 890, y: 300, r: 25, label: "DER-5", color: readCSSVariable(YELLOW_NODE_VAR, "#ffee00"), desc: "Gestor de colas 5", purpleTargets: ["p_right", "p_bottom"], side: "right" }, + { id: "yr_6", x: 965, y: 345, r: 25, label: "DER-6", color: readCSSVariable(YELLOW_NODE_VAR, "#ffff00"), desc: "Canal asíncrono 6", purpleTargets: ["p_right", "p_bottom"], side: "right" }, + { id: "yr_7", x: 890, y: 375, r: 25, label: "DER-7", color: readCSSVariable(YELLOW_NODE_VAR, "#fff01f"), desc: "Salida perimetral 7", purpleTargets: ["p_bottom", "p_right"], side: "right" }, + { id: "yr_8", x: 830, y: 420, r: 26, label: "DER-8", color: readCSSVariable(YELLOW_NODE_VAR, "#ffee00"), desc: "Canal de descarga 8", purpleTargets: ["p_bottom", "p_right"], side: "right" } + ] +}; + +/* ========================================================================== + MOTOR INTERACTIVO Y RENDERIZADO + ========================================================================== */ + +const canvas = document.getElementById("networkCanvas"); +const ctx = canvas.getContext("2d"); +const tooltip = document.getElementById("tooltip"); +const tooltipTitle = document.getElementById("tooltip-title"); +const tooltipDesc = document.getElementById("tooltip-desc"); + +let nodes = []; +let links = []; +let hoveredNode = null; +let dashOffset = 0; + +// Construcción de la estructura +function initNetwork() { + nodes = []; + links = []; + const linkSet = new Set(); // Previene enlaces duplicados entre mismos nodos + + function addLink(fromId, toId, type, curved = false, curvature = 0) { + const keyA = `${fromId}->${toId}`; + const keyB = `${toId}->${fromId}`; + if (linkSet.has(keyA) || linkSet.has(keyB)) return; + linkSet.add(keyA); + links.push({ from: fromId, to: toId, type, curved, curvature }); + } + + // 1. Agregar Nodos Violetas + CONFIG.purpleNodes.forEach(p => { + nodes.push({ + ...p, + type: "purple", + baseX: p.x, + baseY: p.y, + speedOffset: Math.random() * 10 + }); + }); + + // Conectar los violetas entre sí (malla central) + const purples = nodes.filter(n => n.type === "purple"); + for (let i = 0; i < purples.length; i++) { + for (let j = i + 1; j < purples.length; j++) { + addLink(purples[i].id, purples[j].id, "purple-purple", false); + } + } + + // 2. Agregar Nodos Amarillos y crear DOBLE conexión curva hacia los violetas + CONFIG.yellowNodes.forEach((y) => { + nodes.push({ + id: y.id, + x: y.x, + y: y.y, + baseX: y.x, + baseY: y.y, + radius: y.r, + label: y.label, + color: y.color, + desc: y.desc, + side: y.side, + type: "yellow", + speedOffset: Math.random() * 10 + }); + + if (Array.isArray(y.purpleTargets)) { + y.purpleTargets.forEach((targetPurpleId, index) => { + addLink( + targetPurpleId, + y.id, + "purple-yellow", + true, + index === 0 ? 0.16 : -0.16 + ); + }); + } + }); + + // 3. Crear Nodos Verdes Terciarios + const greenPositions = [ + // Lado Izquierdo + { id: "gl_1", x: 190, y: 95, side: "left" }, + { id: "gl_2", x: 260, y: 120, side: "left" }, + { id: "gl_3", x: 140, y: 155, side: "left" }, + { id: "gl_4", x: 75, y: 150, side: "left" }, + { id: "gl_5", x: 130, y: 225, side: "left" }, + { id: "gl_6", x: 65, y: 230, side: "left" }, + { id: "gl_7", x: 195, y: 235, side: "left" }, + { id: "gl_8", x: 60, y: 290, side: "left" }, + { id: "gl_9", x: 165, y: 305, side: "left" }, + { id: "gl_10", x: 75, y: 375, side: "left" }, + { id: "gl_11", x: 140, y: 380, side: "left" }, + { id: "gl_12", x: 145, y: 440, side: "left" }, + { id: "gl_13", x: 235, y: 425, side: "left" }, + + // Lado Derecho + { id: "gr_1", x: 1040, y: 90, side: "right" }, + { id: "gr_2", x: 960, y: 110, side: "right" }, + { id: "gr_3", x: 1025, y: 150, side: "right" }, + { id: "gr_4", x: 1095, y: 155, side: "right" }, + { id: "gr_5", x: 1160, y: 150, side: "right" }, + { id: "gr_6", x: 1030, y: 220, side: "right" }, + { id: "gr_7", x: 1105, y: 215, side: "right" }, + { id: "gr_8", x: 1170, y: 225, side: "right" }, + { id: "gr_9", x: 1060, y: 285, side: "right" }, + { id: "gr_10", x: 1120, y: 305, side: "right" }, + { id: "gr_11", x: 1175, y: 290, side: "right" }, + { id: "gr_12", x: 1025, y: 335, side: "right" }, + { id: "gr_13", x: 1095, y: 350, side: "right" }, + { id: "gr_14", x: 1035, y: 400, side: "right" }, + { id: "gr_15", x: 1085, y: 435, side: "right" }, + { id: "gr_16", x: 1165, y: 385, side: "right" }, + { id: "gr_17", x: 970, y: 425, side: "right" } + ]; + + greenPositions.forEach(g => { + nodes.push({ + id: g.id, + x: g.x, + y: g.y, + baseX: g.x, + baseY: g.y, + radius: 14, + type: "green", + side: g.side, + speedOffset: Math.random() * 10 + }); + }); + + // 4. CONEXIÓN AMARILLOS <-> VERDES (cobertura total en ambas direcciones) + const yellows = nodes.filter(n => n.type === "yellow"); + const greens = nodes.filter(n => n.type === "green"); + + // PASO A: cada amarillo con al menos 1 verde + yellows.forEach((yNode, idx) => { + const candidateGreens = greens.filter(g => g.side === yNode.side); + candidateGreens.sort((a, b) => { + const distA = Math.hypot(a.baseX - yNode.baseX, a.baseY - yNode.baseY); + const distB = Math.hypot(b.baseX - yNode.baseX, b.baseY - yNode.baseY); + return distA - distB; + }); + + if (candidateGreens.length > 0) { + addLink( + yNode.id, + candidateGreens[0].id, + "yellow-green", + true, + idx % 2 === 0 ? 0.20 : -0.20 + ); + } + }); + + // PASO B: cada verde con al menos 1 amarillo + greens.forEach((gNode, idx) => { + const alreadyConnected = links.some( + l => l.type === "yellow-green" && (l.from === gNode.id || l.to === gNode.id) + ); + + const candidateYellows = yellows.filter(y => y.side === gNode.side); + candidateYellows.sort((a, b) => { + const distA = Math.hypot(a.baseX - gNode.baseX, a.baseY - gNode.baseY); + const distB = Math.hypot(b.baseX - gNode.baseX, b.baseY - gNode.baseY); + return distA - distB; + }); + + if (!alreadyConnected && candidateYellows.length > 0) { + addLink( + candidateYellows[0].id, + gNode.id, + "yellow-green", + true, + idx % 2 === 0 ? -0.22 : 0.22 + ); + } else if (candidateYellows.length > 1 && idx % 3 === 0) { + addLink( + candidateYellows[1].id, + gNode.id, + "yellow-green", + true, + idx % 2 === 0 ? 0.26 : -0.26 + ); + } + }); +} + +// Bucle de actualización (flotación en zona segura) +function update(time) { + const maxOffset = 4.5; + + nodes.forEach(node => { + if (hoveredNode && hoveredNode.id === node.id) { + node.x = node.baseX; + node.y = node.baseY; + return; + } + + const t = (time * 0.0016) + node.speedOffset; + node.x = node.baseX + Math.sin(t) * maxOffset; + node.y = node.baseY + Math.cos(t * 0.85) * (maxOffset * 0.75); + }); + + dashOffset -= 0.35; +} + +// Renderizado de líneas curvadas +function drawConnection(source, target, link, isHighlight) { + ctx.save(); + ctx.beginPath(); + ctx.moveTo(source.x, source.y); + + if (link.curved) { + const midX = (source.x + target.x) / 2; + const midY = (source.y + target.y) / 2; + const dx = target.x - source.x; + const dy = target.y - source.y; + const dist = Math.hypot(dx, dy); + + const nx = -dy / dist; + const ny = dx / dist; + + const arcHeight = dist * link.curvature; + const cpX = midX + nx * arcHeight; + const cpY = midY + ny * arcHeight; + + ctx.quadraticCurveTo(cpX, cpY, target.x, target.y); + } else { + ctx.lineTo(target.x, target.y); + } + + if (link.type === "purple-purple") { + ctx.setLineDash([4, 4]); + ctx.lineDashOffset = dashOffset * 0.5; + ctx.strokeStyle = isHighlight ? "#000000" : CONFIG.purpleCoreLineColor; + ctx.lineWidth = isHighlight ? 2.6 : 1.8; + } else if (link.type === "yellow-green") { + ctx.setLineDash([3, 4]); + ctx.lineDashOffset = dashOffset; + ctx.strokeStyle = isHighlight ? CONFIG.linkActiveColor : CONFIG.linkGreenColor; + ctx.lineWidth = isHighlight ? 2.0 : 0.95; + } else { + ctx.setLineDash([3, 4]); + ctx.lineDashOffset = dashOffset; + ctx.strokeStyle = isHighlight ? CONFIG.linkActiveColor : CONFIG.linkLineColor; + ctx.lineWidth = isHighlight ? 2.0 : 1.05; + } + + ctx.stroke(); + ctx.restore(); +} + +// Renderizado del Canvas +function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + const nodeMap = new Map(nodes.map(n => [n.id, n])); + + // 1. Dibujar líneas + links.forEach(link => { + const s = nodeMap.get(link.from); + const t = nodeMap.get(link.to); + if (!s || !t) return; + + const isHighlight = hoveredNode && (hoveredNode.id === s.id || hoveredNode.id === t.id); + drawConnection(s, t, link, isHighlight); + }); + + // 2. Dibujar nodos + nodes.forEach(node => { + ctx.save(); + ctx.beginPath(); + ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2); + + if (node.type === "purple") { + ctx.fillStyle = node.color; + ctx.strokeStyle = "#210036"; + ctx.lineWidth = 1.8; + } else if (node.type === "yellow") { + ctx.fillStyle = node.color; + ctx.strokeStyle = "#383500"; + ctx.lineWidth = 1.3; + } else { + ctx.fillStyle = CONFIG.greenNodesColor; + ctx.strokeStyle = CONFIG.greenStrokeColor; + ctx.lineWidth = 1.1; + } + + if (hoveredNode && hoveredNode.id === node.id) { + ctx.shadowColor = "rgba(0, 0, 0, 0.35)"; + ctx.shadowBlur = 14; + } + + ctx.fill(); + ctx.stroke(); + ctx.restore(); + + // 3. Texto en nodos violetas y amarillos + if (node.label) { + ctx.save(); + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + + if (node.type === "purple") { + ctx.fillStyle = "#ffffff"; + ctx.font = "bold 12.5px system-ui, sans-serif"; + } else if (node.type === "yellow") { + ctx.fillStyle = "#1e1e1e"; + ctx.font = "600 10.5px system-ui, sans-serif"; + } + + ctx.fillText(node.label, node.x, node.y); + ctx.restore(); + } + }); +} + +// Loop 60 FPS +function animate(time) { + update(time); + draw(); + requestAnimationFrame(animate); +} + +// Coordenadas con soporte responsivo +function getCanvasCoordinates(e) { + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + + return { + x: (e.clientX - rect.left) * scaleX, + y: (e.clientY - rect.top) * scaleY, + cssX: e.clientX - rect.left, + cssY: e.clientY - rect.top + }; +} + +// Eventos de Mouse +canvas.addEventListener("mousemove", (e) => { + const { x, y, cssX, cssY } = getCanvasCoordinates(e); + + const targets = nodes.filter(n => n.type === "purple" || n.type === "yellow"); + let found = null; + + for (let node of targets) { + if (Math.hypot(node.x - x, node.y - y) <= node.radius) { + found = node; + break; + } + } + + hoveredNode = found; + + if (hoveredNode) { + canvas.style.cursor = "pointer"; + tooltipTitle.textContent = hoveredNode.label; + tooltipDesc.textContent = hoveredNode.desc; + + tooltip.style.left = `${cssX}px`; + tooltip.style.top = `${cssY - (hoveredNode.radius * (canvas.getBoundingClientRect().height / canvas.height))}px`; + tooltip.classList.remove("hidden"); + } else { + canvas.style.cursor = "default"; + tooltip.classList.add("hidden"); + } +}); + +canvas.addEventListener("mouseleave", () => { + hoveredNode = null; + tooltip.classList.add("hidden"); +}); + +/* ========================================================================== + SOPORTE DE TEMA CLARO / OSCURO + -------------------------------------------------------------------------- + - Re-lee las variables CSS y actualiza CONFIG + nodos vivos. + - Observa cambios de `data-theme` o `class` en y . + ========================================================================== */ + +function refreshColorsFromCSS() { + // 1. Colores globales + CONFIG.greenNodesColor = readCSSVariable(CSS_VARS.greenNodesColor, CONFIG.greenNodesColor); + CONFIG.greenStrokeColor = readCSSVariable(CSS_VARS.greenStrokeColor, CONFIG.greenStrokeColor); + CONFIG.linkLineColor = readCSSVariable(CSS_VARS.linkLineColor, CONFIG.linkLineColor); + CONFIG.linkGreenColor = readCSSVariable(CSS_VARS.linkGreenColor, CONFIG.linkGreenColor); + CONFIG.linkActiveColor = readCSSVariable(CSS_VARS.linkActiveColor, CONFIG.linkActiveColor); + CONFIG.purpleCoreLineColor = readCSSVariable(CSS_VARS.purpleCoreLineColor, CONFIG.purpleCoreLineColor); + + // 2. Colores de violetas en CONFIG + CONFIG.purpleNodes.forEach(p => { + const cssVar = PURPLE_NODE_VARS[p.id]; + if (cssVar) p.color = readCSSVariable(cssVar, p.color); + }); + + // 3. Colores de amarillos en CONFIG + CONFIG.yellowNodes.forEach(y => { + y.color = readCSSVariable(YELLOW_NODE_VAR, y.color); + }); + + // 4. Propagar a los nodos ya instanciados + nodes.forEach(node => { + if (node.type === "purple" && PURPLE_NODE_VARS[node.id]) { + node.color = readCSSVariable(PURPLE_NODE_VARS[node.id], node.color); + } else if (node.type === "yellow") { + node.color = readCSSVariable(YELLOW_NODE_VAR, node.color); + } + // Los verdes leen CONFIG.greenNodesColor directo en draw(), no requieren update propio + }); +} + +function watchThemeChanges() { + const observer = new MutationObserver(() => { + refreshColorsFromCSS(); + }); + + // Observa (habitual en data-theme) y (por si acaso) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme", "class"] + }); + + if (document.body) { + observer.observe(document.body, { + attributes: true, + attributeFilter: ["data-theme", "class"] + }); + } +} + +// Inicialización +initNetwork(); +watchThemeChanges(); +requestAnimationFrame(animate); \ No newline at end of file diff --git a/soluciones.html b/soluciones.html index b3a7479..1c71314 100644 --- a/soluciones.html +++ b/soluciones.html @@ -49,9 +49,9 @@
- RESPUESTAS ESPECÍFICAS -

Soluciones concretas a necesidades definidas

-

+ RESPUESTAS ESPECÍFICAS +

Soluciones concretas a necesidades definidas

+

Si ya sabés exactamente qué servicio estás buscando, aquí encontrarás el detalle técnico de nuestras intervenciones, alcances y valor esperado.

diff --git a/somos-ide.html b/somos-ide.html index 35bbe10..315b267 100644 --- a/somos-ide.html +++ b/somos-ide.html @@ -7,7 +7,8 @@ - + + diff --git a/trabaja-con-nosotros.html b/trabaja-con-nosotros.html index 68a378b..bf7460b 100644 --- a/trabaja-con-nosotros.html +++ b/trabaja-con-nosotros.html @@ -7,7 +7,8 @@ - + +