new site
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
// D3.js Animations for Homepage - Training & Learning Focus
|
||||
function initD3Animation() {
|
||||
const container = d3.select("#d3-animation");
|
||||
if (container.empty()) return;
|
||||
|
||||
const containerRect = container.node().getBoundingClientRect();
|
||||
const width = containerRect.width;
|
||||
const height = containerRect.height || 400;
|
||||
|
||||
// Clear any existing SVG
|
||||
container.selectAll("*").remove();
|
||||
|
||||
const svg = container.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("viewBox", `0 0 ${width} ${height}`)
|
||||
.style("background", "transparent")
|
||||
.style("overflow", "hidden");
|
||||
|
||||
// Create a main group for all elements
|
||||
const mainGroup = svg.append("g")
|
||||
.attr("class", "main-group");
|
||||
|
||||
// Create animated network visualization representing Odoo training modules
|
||||
const nodes = [
|
||||
{ id: "Fundamentos", group: 1, size: 35, level: 1 },
|
||||
{ id: "CRM Básico", group: 2, size: 25, level: 2 },
|
||||
{ id: "Ventas", group: 2, size: 25, level: 2 },
|
||||
{ id: "Inventario", group: 3, size: 25, level: 2 },
|
||||
{ id: "Contabilidad", group: 4, size: 30, level: 2 },
|
||||
{ id: "CRM Avanzado", group: 2, size: 20, level: 3 },
|
||||
{ id: "Reportes", group: 4, size: 20, level: 3 },
|
||||
{ id: "Integraciones", group: 3, size: 25, level: 3 },
|
||||
{ id: "Certificación", group: 1, size: 30, level: 4 },
|
||||
{ id: "Especialización", group: 1, size: 25, level: 4 }
|
||||
];
|
||||
|
||||
const links = [
|
||||
{ source: "Fundamentos", target: "CRM Básico", value: 3 },
|
||||
{ source: "Fundamentos", target: "Ventas", value: 3 },
|
||||
{ source: "Fundamentos", target: "Inventario", value: 3 },
|
||||
{ source: "Fundamentos", target: "Contabilidad", value: 3 },
|
||||
{ source: "CRM Básico", target: "CRM Avanzado", value: 2 },
|
||||
{ source: "Ventas", target: "CRM Avanzado", value: 2 },
|
||||
{ source: "Contabilidad", target: "Reportes", value: 3 },
|
||||
{ source: "Inventario", target: "Integraciones", value: 3 },
|
||||
{ source: "CRM Avanzado", target: "Certificación", value: 3 },
|
||||
{ source: "Reportes", target: "Certificación", value: 2 },
|
||||
{ source: "Integraciones", target: "Especialización", value: 2 },
|
||||
{ source: "Certificación", target: "Especialización", value: 3 }
|
||||
];
|
||||
|
||||
// Color scale with orange theme
|
||||
const color = d3.scaleOrdinal()
|
||||
.domain([1, 2, 3, 4])
|
||||
.range(["#FF6B35", "#FF8A50", "#FFA726", "#FFB74D"]);
|
||||
|
||||
// Add boundary force to keep nodes within container
|
||||
const boundaryForce = () => {
|
||||
nodes.forEach(node => {
|
||||
node.x = Math.max(node.size, Math.min(width - node.size, node.x));
|
||||
node.y = Math.max(node.size, Math.min(height - node.size, node.y));
|
||||
});
|
||||
};
|
||||
|
||||
// Create force simulation with boundaries
|
||||
const simulation = d3.forceSimulation(nodes)
|
||||
.force("link", d3.forceLink(links).id(d => d.id).distance(80))
|
||||
.force("charge", d3.forceManyBody().strength(-250))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||
.force("collision", d3.forceCollide().radius(d => d.size + 8))
|
||||
.force("boundary", boundaryForce);
|
||||
|
||||
// Create links
|
||||
const link = mainGroup.append("g")
|
||||
.attr("class", "links")
|
||||
.selectAll("line")
|
||||
.data(links)
|
||||
.enter().append("line")
|
||||
.attr("stroke", "#FF6B35")
|
||||
.attr("stroke-opacity", 0.3)
|
||||
.attr("stroke-width", d => Math.sqrt(d.value));
|
||||
|
||||
// Create nodes container
|
||||
const node = mainGroup.append("g")
|
||||
.attr("class", "nodes")
|
||||
.selectAll("g")
|
||||
.data(nodes)
|
||||
.enter().append("g")
|
||||
.call(d3.drag()
|
||||
.on("start", dragstarted)
|
||||
.on("drag", dragged)
|
||||
.on("end", dragended));
|
||||
|
||||
// Add circles
|
||||
node.append("circle")
|
||||
.attr("r", d => d.size)
|
||||
.attr("fill", d => color(d.group))
|
||||
.attr("stroke", "#1a1a1a")
|
||||
.attr("stroke-width", 2)
|
||||
.style("cursor", "pointer")
|
||||
.on("mouseover", function(event, d) {
|
||||
d3.select(this)
|
||||
.transition()
|
||||
.duration(200)
|
||||
.attr("r", d.size * 1.2);
|
||||
})
|
||||
.on("mouseout", function(event, d) {
|
||||
d3.select(this)
|
||||
.transition()
|
||||
.duration(200)
|
||||
.attr("r", d.size);
|
||||
});
|
||||
|
||||
// Add text labels
|
||||
node.append("text")
|
||||
.text(d => d.id)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dy", ".35em")
|
||||
.style("fill", "#1a1a1a")
|
||||
.style("font-size", "11px")
|
||||
.style("font-weight", "700")
|
||||
.style("pointer-events", "none");
|
||||
|
||||
// Add animated pulses
|
||||
node.append("circle")
|
||||
.attr("r", d => d.size)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", d => color(d.group))
|
||||
.attr("stroke-width", 2)
|
||||
.attr("opacity", 0)
|
||||
.style("pointer-events", "none")
|
||||
.each(function(d, i) {
|
||||
d3.select(this)
|
||||
.transition()
|
||||
.delay(i * 200)
|
||||
.duration(2000)
|
||||
.attr("r", d => d.size * 2)
|
||||
.attr("opacity", 0)
|
||||
.on("end", function repeat() {
|
||||
d3.select(this)
|
||||
.attr("r", d.size)
|
||||
.attr("opacity", 0.5)
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.attr("r", d.size * 2)
|
||||
.attr("opacity", 0)
|
||||
.on("end", repeat);
|
||||
});
|
||||
});
|
||||
|
||||
// Update positions on tick
|
||||
simulation.on("tick", () => {
|
||||
// Apply boundary force
|
||||
boundaryForce();
|
||||
|
||||
link
|
||||
.attr("x1", d => d.source.x)
|
||||
.attr("y1", d => d.source.y)
|
||||
.attr("x2", d => d.target.x)
|
||||
.attr("y2", d => d.target.y);
|
||||
|
||||
node
|
||||
.attr("transform", d => `translate(${d.x},${d.y})`);
|
||||
});
|
||||
|
||||
// Drag functions with boundaries
|
||||
function dragstarted(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
}
|
||||
|
||||
function dragged(event, d) {
|
||||
d.fx = Math.max(d.size, Math.min(width - d.size, event.x));
|
||||
d.fy = Math.max(d.size, Math.min(height - d.size, event.y));
|
||||
}
|
||||
|
||||
function dragended(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0);
|
||||
d.fx = null;
|
||||
d.fy = null;
|
||||
}
|
||||
|
||||
// Add floating particles with orange theme
|
||||
const particles = mainGroup.append("g")
|
||||
.attr("class", "particles");
|
||||
|
||||
function createParticle() {
|
||||
const particle = particles.append("circle")
|
||||
.attr("r", Math.random() * 2 + 1)
|
||||
.attr("cx", Math.random() * width)
|
||||
.attr("cy", height + 5)
|
||||
.attr("fill", "#FF6B35")
|
||||
.attr("opacity", 0.2);
|
||||
|
||||
particle.transition()
|
||||
.duration(Math.random() * 8000 + 4000)
|
||||
.ease(d3.easeLinear)
|
||||
.attr("cy", -5)
|
||||
.attr("opacity", 0)
|
||||
.remove();
|
||||
}
|
||||
|
||||
// Create particles periodically
|
||||
setInterval(createParticle, 500);
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initD3Animation();
|
||||
|
||||
// Reinitialize on window resize
|
||||
let resizeTimer;
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function() {
|
||||
initD3Animation();
|
||||
}, 250);
|
||||
});
|
||||
});
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user