cambios esteticos index+desafios+sluciones+ecosistema

This commit is contained in:
2026-09-26 20:30:03 -03:00
parent 1e2595275f
commit 7e930760e1
13 changed files with 2021 additions and 302 deletions
+264 -74
View File
@@ -1,23 +1,30 @@
// 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"
greenNodesColor: "--ide-primary",
greenStrokeColor: "--ide-primary-light",
linkLineColor: "--ide-primary-line",
linkGreenColor: "--ide-primary-line",
linkGreenToGreenColor: "--ide-primary-light2", // Color para líneas entre nodos verdes
linkActiveColor: "--ide-primary-light",
purpleCoreLineColor: "--ide-primary-line",
shadowCanva: "--shadow-canva",
textMain: "--text-main",
textInverse: "--text-inverse",
centerBoxBg: "--ide-center-bg", // Fondo del recuadro central
centerBoxBorder: "--ide-center-border", // Borde del recuadro central
centerBoxText: "--ide-center-text" // Texto del recuadro central
};
// 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"
p_top: "--ide-excellence",
p_bottom: "--ide-academy",
p_left: "--ide-projects",
p_right: "--ide-digital"
};
// Variable CSS común para todos los amarillos
const YELLOW_NODE_VAR = "--canvas-yellow-node-color";
const YELLOW_NODE_VAR = "--ide-primary-dark";
/* ==========================================================================
UTILIDAD: LEER VARIABLES CSS
@@ -36,70 +43,94 @@ function readCSSVariable(variableName, fallback = "") {
const CONFIG = {
// 1. Color global configurable para TODOS los nodos verdes
greenNodesColor: readCSSVariable(CSS_VARS.greenNodesColor, "#9b1717"),
greenStrokeColor: readCSSVariable(CSS_VARS.greenStrokeColor, "#630b5f"),
greenNodesColor: readCSSVariable(CSS_VARS.greenNodesColor),
greenStrokeColor: readCSSVariable(CSS_VARS.greenStrokeColor),
// 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)"),
linkLineColor: readCSSVariable(CSS_VARS.linkLineColor),
linkGreenColor: readCSSVariable(CSS_VARS.linkGreenColor),
linkGreenToGreenColor: readCSSVariable(CSS_VARS.linkGreenToGreenColor, readCSSVariable(CSS_VARS.linkGreenColor, "#24333E")),
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)
// 3. Estilos del recuadro central "EL TODO"
centerBoxBg: readCSSVariable(CSS_VARS.centerBoxBg, readCSSVariable(YELLOW_NODE_VAR, "#1b2631")),
centerBoxBorder: readCSSVariable(CSS_VARS.centerBoxBorder, readCSSVariable(CSS_VARS.purpleCoreLineColor, "#3a5160")),
centerBoxText: readCSSVariable(CSS_VARS.centerBoxText, readCSSVariable(CSS_VARS.textInverse, "#ffffff")),
// Datos del componente central
centerBox: {
id: "center_box",
label: "EL TODO",
desc: "Núcleo integrador central del sistema.",
x: 615,
y: 260,
baseX: 615,
baseY: 260,
width: 104,
height: 36,
borderRadius: 8,
speedOffset: Math.random() * 10
},
// 4. 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"),
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: "Núcleo S",
color: readCSSVariable(PURPLE_NODE_VARS.p_bottom, "#9900d8"),
label: "Academy",
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: "Núcleo O",
color: readCSSVariable(PURPLE_NODE_VARS.p_left, "#7e00b3"),
label: "Project",
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: "Núcleo E",
color: readCSSVariable(PURPLE_NODE_VARS.p_right, "#9400cf"),
label: "Digital",
color: readCSSVariable(PURPLE_NODE_VARS.p_right),
desc: "Concentrador oriental hacia subredes de distribución."
}
],
// 4. Nodos AMARILLOS: Cada uno conectado a 2 VIOLETAS
// 5. 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" },
{ 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: "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" }
{ 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" }
]
};
@@ -199,7 +230,7 @@ function initNetwork() {
{ id: "gl_13", x: 235, y: 425, side: "left" },
// Lado Derecho
{ id: "gr_1", x: 1040, y: 90, side: "right" },
{ id: "gr_1", x: 1060, 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" },
@@ -232,27 +263,61 @@ function initNetwork() {
});
});
// 4. CONEXIÓN AMARILLOS <-> VERDES (cobertura total en ambas direcciones)
// 4. CONEXIÓN AMARILLOS <-> VERDES (Múltiples conexiones por verde)
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);
// Configura cuántos amarillos conectar a cada verde (recomendado: 2 o 3)
const YELLOWS_PER_GREEN = 2;
greens.forEach((gNode, gIdx) => {
// 1. Filtrar amarillos que pertenezcan al mismo sector (left/right)
const candidateYellows = yellows.filter(y => y.side === gNode.side);
// 2. Ordenar de menor a mayor distancia al nodo verde
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 (candidateGreens.length > 0) {
// 3. Tomar los N amarillos más cercanos
const targets = candidateYellows.slice(0, YELLOWS_PER_GREEN);
targets.forEach((yNode, targetRank) => {
// Alternar arco: el primero arquea hacia un lado y el segundo hacia el opuesto
// Esto previene que las líneas coincidan o se solapen
const direction = (gIdx + targetRank) % 2 === 0 ? 1 : -1;
const baseCurvature = 0.16 + (targetRank * 0.07);
const curvature = baseCurvature * direction;
addLink(
yNode.id,
candidateGreens[0].id,
gNode.id,
"yellow-green",
true,
idx % 2 === 0 ? 0.20 : -0.20
curvature
);
});
});
// PASO DE SEGURIDAD: asegurar que ningún amarillo quede sin al menos un verde
yellows.forEach(yNode => {
const hasConnection = links.some(
l => l.type === "yellow-green" && (l.from === yNode.id || l.to === yNode.id)
);
if (!hasConnection) {
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, 0.20);
}
}
});
@@ -287,6 +352,27 @@ function initNetwork() {
);
}
});
// 5. CONEXIÓN VERDES <-> VERDES (mismo sector, líneas rectas)
["left", "right"].forEach(side => {
const sectorGreens = greens.filter(g => g.side === side);
sectorGreens.forEach(gNode => {
const neighbors = sectorGreens
.filter(other => other.id !== gNode.id)
.map(other => ({
id: other.id,
dist: Math.hypot(other.baseX - gNode.baseX, other.baseY - gNode.baseY)
}))
.sort((a, b) => a.dist - b.dist);
// Conecta con los 4 vecinos más cercanos del mismo sector para formar la malla limpia
neighbors.slice(0, 5).forEach(neighbor => {
if (neighbor.dist <= 130) {
addLink(gNode.id, neighbor.id, "green-green", false);
}
});
});
});
}
// Bucle de actualización (flotación en zona segura)
@@ -305,10 +391,29 @@ function update(time) {
node.y = node.baseY + Math.cos(t * 0.85) * (maxOffset * 0.75);
});
// Movimiento coordinado de "EL TODO" según el centroide de los nodos principales
const purples = nodes.filter(n => n.type === "purple");
if (purples.length > 0 && CONFIG.centerBox) {
const avgX = purples.reduce((sum, p) => sum + p.x, 0) / purples.length;
const avgY = purples.reduce((sum, p) => sum + p.y, 0) / purples.length;
if (hoveredNode && hoveredNode.id === CONFIG.centerBox.id) {
CONFIG.centerBox.x = 615;
CONFIG.centerBox.y = 260;
} else {
const t = (time * 0.0016) + CONFIG.centerBox.speedOffset;
CONFIG.centerBox.x = avgX + Math.sin(t) * (maxOffset * 0.45);
CONFIG.centerBox.y = avgY + Math.cos(t * 0.85) * (maxOffset * 0.35);
}
}
//aceleracion de la red
const isCenterActive = hoveredNode && CONFIG.centerBox && hoveredNode.id === CONFIG.centerBox.id;
dashOffset -= isCenterActive ? 0.95 : 0.35;
dashOffset -= 0.35;
}
// Renderizado de líneas curvadas
// Renderizado de líneas curvadas y rectas
function drawConnection(source, target, link, isHighlight) {
ctx.save();
ctx.beginPath();
@@ -336,13 +441,18 @@ function drawConnection(source, target, link, isHighlight) {
if (link.type === "purple-purple") {
ctx.setLineDash([4, 4]);
ctx.lineDashOffset = dashOffset * 0.5;
ctx.strokeStyle = isHighlight ? "#000000" : CONFIG.purpleCoreLineColor;
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 if (link.type === "green-green") {
ctx.setLineDash([3, 4]);
ctx.lineDashOffset = dashOffset * 0.8;
ctx.strokeStyle = isHighlight ? CONFIG.linkActiveColor : CONFIG.linkGreenToGreenColor;
ctx.lineWidth = isHighlight ? 1.8 : 0.9;
} else {
ctx.setLineDash([3, 4]);
ctx.lineDashOffset = dashOffset;
@@ -354,22 +464,86 @@ function drawConnection(source, target, link, isHighlight) {
ctx.restore();
}
// Dibujo del recuadro central "EL TODO"
function drawCenterBox() {
const box = CONFIG.centerBox;
if (!box) return;
const w = box.width;
const h = box.height;
const r = box.borderRadius;
const x = box.x - w / 2;
const y = box.y - h / 2;
const isBoxHovered = hoveredNode && hoveredNode.id === box.id;
ctx.save();
ctx.beginPath();
if (ctx.roundRect) {
ctx.roundRect(x, y, w, h, r);
} else {
// Compatibilidad en navegadores antiguos
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
}
if (isBoxHovered) {
ctx.shadowColor = CONFIG.shadowCanva;
ctx.shadowBlur = 14;
} else {
ctx.shadowColor = "rgba(0, 0, 0, 0.25)";
ctx.shadowBlur = 6;
}
ctx.fillStyle = CONFIG.centerBoxBg;
ctx.fill();
ctx.strokeStyle = isBoxHovered ? CONFIG.linkActiveColor : CONFIG.centerBoxBorder;
ctx.lineWidth = isBoxHovered ? 2.0 : 1.4;
ctx.stroke();
ctx.restore();
// Texto "EL TODO"
ctx.save();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = CONFIG.centerBoxText;
ctx.font = "bold 11.5px system-ui, sans-serif";
ctx.fillText(box.label, box.x, box.y);
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
const isCenterBoxHovered = hoveredNode && CONFIG.centerBox && hoveredNode.id === CONFIG.centerBox.id;
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);
// Se ilumina si estás sobre un nodo conectado O si estás sobre "EL TODO"
const isHighlight = isCenterBoxHovered || (hoveredNode && (hoveredNode.id === s.id || hoveredNode.id === t.id));
drawConnection(s, t, link, isHighlight);
});
});;
// 2. Dibujar nodos
// 2. Dibujar recuadro central "EL TODO" (situado en el plano intermedio para enmascarar cruces)
drawCenterBox();
// 3. Dibujar nodos
nodes.forEach(node => {
ctx.save();
ctx.beginPath();
@@ -377,11 +551,11 @@ function draw() {
if (node.type === "purple") {
ctx.fillStyle = node.color;
ctx.strokeStyle = "#210036";
ctx.strokeStyle = node.color;
ctx.lineWidth = 1.8;
} else if (node.type === "yellow") {
ctx.fillStyle = node.color;
ctx.strokeStyle = "#383500";
ctx.strokeStyle = node.color;
ctx.lineWidth = 1.3;
} else {
ctx.fillStyle = CONFIG.greenNodesColor;
@@ -390,7 +564,7 @@ function draw() {
}
if (hoveredNode && hoveredNode.id === node.id) {
ctx.shadowColor = "rgba(0, 0, 0, 0.35)";
ctx.shadowColor = CONFIG.shadowCanva;
ctx.shadowBlur = 14;
}
@@ -398,7 +572,7 @@ function draw() {
ctx.stroke();
ctx.restore();
// 3. Texto en nodos violetas y amarillos
// 4. Texto en nodos violetas y amarillos
if (node.label) {
ctx.save();
ctx.textAlign = "center";
@@ -408,7 +582,7 @@ function draw() {
ctx.fillStyle = "#ffffff";
ctx.font = "bold 12.5px system-ui, sans-serif";
} else if (node.type === "yellow") {
ctx.fillStyle = "#1e1e1e";
ctx.fillStyle = "#ffffff";
ctx.font = "600 10.5px system-ui, sans-serif";
}
@@ -453,12 +627,29 @@ canvas.addEventListener("mousemove", (e) => {
}
}
// Detección sobre el recuadro central "EL TODO"
if (!found && CONFIG.centerBox) {
const box = CONFIG.centerBox;
const halfW = box.width / 2;
const halfH = box.height / 2;
if (x >= box.x - halfW && x <= box.x + halfW && y >= box.y - halfH && y <= box.y + halfH) {
found = {
id: box.id,
label: box.label,
desc: box.desc,
radius: halfH,
x: box.x,
y: box.y
};
}
}
hoveredNode = found;
if (hoveredNode) {
canvas.style.cursor = "pointer";
tooltipTitle.textContent = hoveredNode.label;
tooltipDesc.textContent = hoveredNode.desc;
tooltipDesc.textContent = hoveredNode.desc || "";
tooltip.style.left = `${cssX}px`;
tooltip.style.top = `${cssY - (hoveredNode.radius * (canvas.getBoundingClientRect().height / canvas.height))}px`;
@@ -476,19 +667,20 @@ canvas.addEventListener("mouseleave", () => {
/* ==========================================================================
SOPORTE DE TEMA CLARO / OSCURO
--------------------------------------------------------------------------
- Re-lee las variables CSS y actualiza CONFIG + nodos vivos.
- Observa cambios de `data-theme` o `class` en <html> y <body>.
========================================================================== */
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);
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.linkGreenToGreenColor = readCSSVariable(CSS_VARS.linkGreenToGreenColor, CONFIG.linkGreenToGreenColor);
CONFIG.linkActiveColor = readCSSVariable(CSS_VARS.linkActiveColor, CONFIG.linkActiveColor);
CONFIG.purpleCoreLineColor = readCSSVariable(CSS_VARS.purpleCoreLineColor, CONFIG.purpleCoreLineColor);
CONFIG.centerBoxBg = readCSSVariable(CSS_VARS.centerBoxBg, CONFIG.centerBoxBg);
CONFIG.centerBoxBorder = readCSSVariable(CSS_VARS.centerBoxBorder, CONFIG.centerBoxBorder);
CONFIG.centerBoxText = readCSSVariable(CSS_VARS.centerBoxText, CONFIG.centerBoxText);
// 2. Colores de violetas en CONFIG
CONFIG.purpleNodes.forEach(p => {
@@ -508,7 +700,6 @@ function refreshColorsFromCSS() {
} 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
});
}
@@ -517,7 +708,6 @@ function watchThemeChanges() {
refreshColorsFromCSS();
});
// Observa <html> (habitual en data-theme) y <body> (por si acaso)
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme", "class"]