Complete website optimization: Color scheme standardization and hero page enhancement
- STANDARDIZED COLOR SCHEMES: Unified all service pages to use consistent purple theme (#6d28d9) - Removed theme-specific body classes from automation-flows.html and agentic-ai.html - Removed inline CSS color overrides from machine-learning.html, generative-ai.html, and data-analysis.html - Updated 15+ instances of hardcoded border colors to use rgba(var(--primary-color-rgb), 0.3) - Updated 5+ hover effects to use rgba(var(--primary-color-rgb), 0.4) for consistent glow colors - All service pages now maintain visual consistency with the main website - ENHANCED HERO PAGE: Improved data node scattering for better organic distribution - Repositioned core service nodes (ML, AI, Analytics, Automation) for natural scatter pattern - Enhanced industry nodes positioning with organic right-side distribution - Improved strategic nodes placement across right quadrant for better visual balance - Added varied animation delays and floating patterns for different node groups - Implemented micro-positioning adjustments for more natural appearance - Created size variations and rotation effects for enhanced organic feel - NEW FEATURES ADDED: - Enhanced CSS animations with varied floating patterns - Improved node interaction effects with staggered hover animations - Better responsive design for mobile devices - New documentation files for color standardization completion - TECHNICAL IMPROVEMENTS: - Updated hero-animation.css with enhanced organic scatter positioning - Maintained all existing functionality while improving visual aesthetics - Ensured cross-browser compatibility and mobile responsiveness
This commit is contained in:
761
js/data-node-interactions.js
Normal file
761
js/data-node-interactions.js
Normal file
@@ -0,0 +1,761 @@
|
||||
/**
|
||||
* Enhanced Unified Neural Network Animation and Data Node Interactive Effects
|
||||
* This file handles both the canvas background animation and data node interactions
|
||||
* with advanced interactive elements and dynamic visual effects
|
||||
*/
|
||||
|
||||
// Neural Network Canvas Animation Class
|
||||
class NeuralNetworkAnimation {
|
||||
constructor() {
|
||||
this.canvas = document.querySelector('.mesh-canvas');
|
||||
this.ctx = this.canvas ? this.canvas.getContext('2d') : null;
|
||||
this.nodes = [];
|
||||
this.connections = [];
|
||||
this.mousePos = { x: 0, y: 0 };
|
||||
this.animationId = null;
|
||||
|
||||
if (this.ctx) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupCanvas();
|
||||
this.createNodes();
|
||||
this.setupInteractions();
|
||||
this.setupResize();
|
||||
this.animate();
|
||||
}
|
||||
|
||||
setupCanvas() {
|
||||
const heroSection = document.getElementById('hero');
|
||||
if (heroSection) {
|
||||
const rect = heroSection.getBoundingClientRect();
|
||||
this.canvas.width = rect.width;
|
||||
this.canvas.height = rect.height;
|
||||
|
||||
this.canvas.style.width = '100%';
|
||||
this.canvas.style.height = '100%';
|
||||
}
|
||||
}
|
||||
|
||||
setupResize() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.setupCanvas();
|
||||
this.nodes.forEach(node => {
|
||||
node.x = Math.min(node.x, this.canvas.width);
|
||||
node.y = Math.min(node.y, this.canvas.height);
|
||||
});
|
||||
});
|
||||
}
|
||||
createNodes() {
|
||||
const nodeCount = 40;
|
||||
this.nodes = [];
|
||||
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
this.nodes.push({
|
||||
x: Math.random() * this.canvas.width,
|
||||
y: Math.random() * this.canvas.height,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
radius: Math.random() * 3 + 1,
|
||||
opacity: Math.random() * 0.5 + 0.2,
|
||||
pulsePhase: Math.random() * Math.PI * 2,
|
||||
energy: Math.random()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateNodes() {
|
||||
this.nodes.forEach(node => {
|
||||
// Update position
|
||||
node.x += node.vx;
|
||||
node.y += node.vy;
|
||||
|
||||
// Bounce off edges
|
||||
if (node.x <= 0 || node.x >= this.canvas.width) node.vx *= -1;
|
||||
if (node.y <= 0 || node.y >= this.canvas.height) node.vy *= -1;
|
||||
|
||||
// Keep nodes in bounds
|
||||
node.x = Math.max(0, Math.min(this.canvas.width, node.x));
|
||||
node.y = Math.max(0, Math.min(this.canvas.height, node.y));
|
||||
|
||||
// Update pulse
|
||||
node.pulsePhase += 0.02;
|
||||
node.energy = 0.5 + 0.5 * Math.sin(node.pulsePhase);
|
||||
});
|
||||
}
|
||||
|
||||
drawNodes() {
|
||||
this.nodes.forEach(node => {
|
||||
const gradient = this.ctx.createRadialGradient(
|
||||
node.x, node.y, 0,
|
||||
node.x, node.y, node.radius * 3
|
||||
);
|
||||
|
||||
const alpha = node.opacity * node.energy;
|
||||
gradient.addColorStop(0, `rgba(59, 130, 246, ${alpha})`);
|
||||
gradient.addColorStop(0.5, `rgba(139, 92, 246, ${alpha * 0.7})`);
|
||||
gradient.addColorStop(1, `rgba(236, 72, 153, 0)`);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
|
||||
// Core dot
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(node.x, node.y, node.radius * 0.3, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.8})`;
|
||||
this.ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
drawConnections() {
|
||||
const maxDistance = 120;
|
||||
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
for (let j = i + 1; j < this.nodes.length; j++) {
|
||||
const nodeA = this.nodes[i];
|
||||
const nodeB = this.nodes[j];
|
||||
|
||||
const dx = nodeB.x - nodeA.x;
|
||||
const dy = nodeB.y - nodeA.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < maxDistance) {
|
||||
const opacity = (1 - distance / maxDistance) * 0.3;
|
||||
const energyBoost = (nodeA.energy + nodeB.energy) / 2;
|
||||
|
||||
// Create dynamic gradient line
|
||||
const gradient = this.ctx.createLinearGradient(
|
||||
nodeA.x, nodeA.y, nodeB.x, nodeB.y
|
||||
);
|
||||
|
||||
gradient.addColorStop(0, `rgba(59, 130, 246, ${opacity * energyBoost})`);
|
||||
gradient.addColorStop(0.5, `rgba(139, 92, 246, ${opacity * energyBoost * 0.8})`);
|
||||
gradient.addColorStop(1, `rgba(236, 72, 153, ${opacity * energyBoost * 0.6})`);
|
||||
|
||||
this.ctx.strokeStyle = gradient;
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.globalCompositeOperation = 'lighter';
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(nodeA.x, nodeA.y);
|
||||
this.ctx.lineTo(nodeB.x, nodeB.y);
|
||||
this.ctx.stroke();
|
||||
|
||||
this.ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// Data pulse along connection
|
||||
if (Math.random() < 0.01) {
|
||||
this.createDataPulse(nodeA, nodeB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createDataPulse(nodeA, nodeB) {
|
||||
const pulse = {
|
||||
startX: nodeA.x,
|
||||
startY: nodeA.y,
|
||||
endX: nodeB.x,
|
||||
endY: nodeB.y,
|
||||
progress: 0,
|
||||
speed: 0.02,
|
||||
life: 1
|
||||
};
|
||||
|
||||
this.dataPulses = this.dataPulses || [];
|
||||
this.dataPulses.push(pulse);
|
||||
}
|
||||
|
||||
updateDataPulses() {
|
||||
if (!this.dataPulses) return;
|
||||
|
||||
this.dataPulses = this.dataPulses.filter(pulse => {
|
||||
pulse.progress += pulse.speed;
|
||||
pulse.life -= 0.01;
|
||||
|
||||
if (pulse.progress >= 1 || pulse.life <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Draw pulse
|
||||
const x = pulse.startX + (pulse.endX - pulse.startX) * pulse.progress;
|
||||
const y = pulse.startY + (pulse.endY - pulse.startY) * pulse.progress;
|
||||
|
||||
const gradient = this.ctx.createRadialGradient(x, y, 0, x, y, 6);
|
||||
gradient.addColorStop(0, `rgba(255, 255, 255, ${pulse.life})`);
|
||||
gradient.addColorStop(1, `rgba(59, 130, 246, 0)`);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(x, y, 4, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseMove(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
this.mousePos.x = e.clientX - rect.left;
|
||||
this.mousePos.y = e.clientY - rect.top;
|
||||
|
||||
// Create attraction effect
|
||||
this.nodes.forEach(node => {
|
||||
const dx = this.mousePos.x - node.x;
|
||||
const dy = this.mousePos.y - node.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
const force = (100 - distance) / 100 * 0.01;
|
||||
node.vx += dx * force;
|
||||
node.vy += dy * force;
|
||||
node.energy = Math.min(1, node.energy + 0.1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animate() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Add subtle background gradient
|
||||
const bgGradient = this.ctx.createRadialGradient(
|
||||
this.canvas.width / 2, this.canvas.height / 2, 0,
|
||||
this.canvas.width / 2, this.canvas.height / 2, Math.max(this.canvas.width, this.canvas.height) / 2
|
||||
);
|
||||
bgGradient.addColorStop(0, 'rgba(59, 130, 246, 0.02)');
|
||||
bgGradient.addColorStop(1, 'rgba(139, 92, 246, 0.01)');
|
||||
|
||||
this.ctx.fillStyle = bgGradient;
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.updateNodes();
|
||||
this.drawConnections();
|
||||
this.drawNodes();
|
||||
this.updateDataPulses();
|
||||
|
||||
this.animationId = requestAnimationFrame(() => this.animate());
|
||||
}
|
||||
|
||||
setupInteractions() {
|
||||
// Mouse interaction only - data node interactions handled by DataNodeInteractions class
|
||||
this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data Node Interactive Effects Class
|
||||
class DataNodeInteractions {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
init() {
|
||||
// Wait for DOM to be fully loaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
this.initializeInteractions();
|
||||
});
|
||||
} else {
|
||||
this.initializeInteractions();
|
||||
}
|
||||
}
|
||||
initializeInteractions() {
|
||||
this.setupTooltips();
|
||||
this.setupHoverEffects();
|
||||
this.setupClickEffects();
|
||||
this.addRequiredStyles();
|
||||
}
|
||||
setupTooltips() {
|
||||
const dataNodes = document.querySelectorAll('.data-node');
|
||||
|
||||
// First, remove any existing tooltip elements
|
||||
document.querySelectorAll('.data-node-tooltip').forEach(tooltip => {
|
||||
if (tooltip.parentNode) {
|
||||
tooltip.parentNode.removeChild(tooltip);
|
||||
}
|
||||
});
|
||||
|
||||
dataNodes.forEach((node, index) => {
|
||||
// Create tooltip element
|
||||
const tooltip = this.createTooltip(node);
|
||||
document.body.appendChild(tooltip); // Append to body instead of node for better positioning
|
||||
|
||||
// Store reference to the tooltip on the node
|
||||
node.tooltip = tooltip;
|
||||
|
||||
// Add hover events for tooltip
|
||||
node.addEventListener('mouseenter', (e) => {
|
||||
this.showTooltip(tooltip, node, e);
|
||||
this.triggerHoverEffect(node);
|
||||
});
|
||||
|
||||
node.addEventListener('mouseleave', () => {
|
||||
this.hideTooltip(tooltip);
|
||||
this.resetHoverEffect(node);
|
||||
});
|
||||
|
||||
// Update tooltip position on mouse move - only if needed
|
||||
node.addEventListener('mousemove', (e) => {
|
||||
// Only update on significant mouse movement to prevent jitter
|
||||
if (!node.lastMouseX || Math.abs(e.clientX - node.lastMouseX) > 5 ||
|
||||
!node.lastMouseY || Math.abs(e.clientY - node.lastMouseY) > 5) {
|
||||
this.updateTooltipPosition(tooltip, node, e);
|
||||
node.lastMouseX = e.clientX;
|
||||
node.lastMouseY = e.clientY;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createTooltip(node) {
|
||||
const techType = node.getAttribute('data-tech');
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'data-node-tooltip';
|
||||
|
||||
// Enhanced concise descriptions for each technology
|
||||
const descriptions = {
|
||||
'Machine Learning': 'Building intelligent systems that learn from data to predict outcomes and automate decisions.',
|
||||
'Generative AI': 'Creating novel content from text and images to code, unlocking new creative possibilities.',
|
||||
'Data Analytics': 'Transforming raw data into actionable business insights to drive strategic decisions.',
|
||||
'Automation': 'Designing intelligent workflows that increase efficiency and free up human resources.',
|
||||
'Finance': 'Using AI for risk assessment, fraud detection, and optimized investment strategies.',
|
||||
'Healthcare': 'Enabling faster diagnoses, personalized treatments, and improved patient care.',
|
||||
'Retail': 'Powering personalized shopping experiences, inventory optimization, and demand forecasting.',
|
||||
'Manufacturing': 'Enhancing production efficiency, quality control, and supply chain optimization.',
|
||||
'Agentic AI': 'Developing autonomous AI agents that perceive environments and take goal-driven actions.',
|
||||
'Marketing': 'Creating data-driven campaigns, customer segmentation, and conversion optimization.',
|
||||
'Media & Entertainment': 'Enabling content creation, audience engagement, and personalized recommendations.',
|
||||
'Travel': 'Implementing smart pricing strategies and enhancing customer travel experiences.',
|
||||
'Education': 'Delivering personalized learning paths and educational content innovation.',
|
||||
'Technology': 'Providing cutting-edge AI development and integration services for tech companies.'
|
||||
};
|
||||
|
||||
tooltip.innerHTML = `
|
||||
<div class="tooltip-title">${techType}</div>
|
||||
<div class="tooltip-description">${descriptions[techType] || 'AI-powered solutions and innovations for business transformation.'}</div>
|
||||
<div class="tooltip-arrow"></div>
|
||||
`;
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
showTooltip(tooltip, node, event) {
|
||||
// Hide any other visible tooltips first
|
||||
document.querySelectorAll('.data-node-tooltip').forEach(otherTooltip => {
|
||||
if (otherTooltip !== tooltip && otherTooltip.style.visibility === 'visible') {
|
||||
otherTooltip.style.opacity = '0';
|
||||
otherTooltip.style.visibility = 'hidden';
|
||||
otherTooltip.style.transform = 'scale(0.95)';
|
||||
}
|
||||
});
|
||||
|
||||
// Position the tooltip before showing it
|
||||
this.updateTooltipPosition(tooltip, node, event);
|
||||
|
||||
// Reset any ongoing transitions with enhanced animation
|
||||
tooltip.style.transition = 'all 0.3s cubic-bezier(0.2, 0, 0.2, 1)'; // Smoother easing
|
||||
tooltip.style.opacity = '1';
|
||||
tooltip.style.visibility = 'visible';
|
||||
tooltip.style.transform = 'scale(1)';
|
||||
|
||||
// Add a subtle animation highlight
|
||||
setTimeout(() => {
|
||||
tooltip.style.boxShadow = '0 12px 35px rgba(0, 0, 0, 0.7), 0 0 20px rgba(79, 150, 255, 0.4)';
|
||||
}, 50);
|
||||
}
|
||||
hideTooltip(tooltip) {
|
||||
// Smooth fade out
|
||||
tooltip.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 1, 1)';
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.visibility = 'hidden';
|
||||
tooltip.style.transform = 'scale(0.95)';
|
||||
tooltip.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(59, 130, 246, 0.3)';
|
||||
}
|
||||
updateTooltipPosition(tooltip, node, event) {
|
||||
const rect = node.getBoundingClientRect();
|
||||
|
||||
// Ensure the tooltip has proper dimensions for calculation
|
||||
tooltip.style.visibility = 'hidden';
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.display = 'block';
|
||||
|
||||
const tooltipRect = tooltip.getBoundingClientRect();
|
||||
|
||||
// Position tooltip with a fixed offset from the node to avoid affecting flow
|
||||
let left = rect.left + (rect.width / 2);
|
||||
let top = rect.top - tooltipRect.height - 25; // Increased distance above node
|
||||
|
||||
// Adjust if tooltip goes off screen
|
||||
const viewport = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
|
||||
// Check node position and adjust tooltip accordingly
|
||||
const isRightSide = rect.left > (viewport.width / 2);
|
||||
const isTopHalf = rect.top < (viewport.height / 2);
|
||||
|
||||
// Apply smart positioning based on quadrant location
|
||||
if (isRightSide) {
|
||||
left = Math.min(left + 20, viewport.width - tooltipRect.width - 20);
|
||||
} else {
|
||||
left = Math.max(left - 20, 20);
|
||||
}
|
||||
|
||||
// Keep the tooltip centered relative to the node
|
||||
left = rect.left + (rect.width / 2);
|
||||
|
||||
// Ensure tooltip stays within viewport bounds
|
||||
const margin = 20;
|
||||
if (left < margin) {
|
||||
left = margin;
|
||||
}
|
||||
if (left + tooltipRect.width > viewport.width - margin) {
|
||||
left = viewport.width - tooltipRect.width - margin;
|
||||
}
|
||||
|
||||
// If not enough room above, show below
|
||||
if (top < margin) {
|
||||
top = rect.bottom + 25; // Place below with distance
|
||||
tooltip.classList.add('tooltip-below');
|
||||
} else {
|
||||
tooltip.classList.remove('tooltip-below');
|
||||
}
|
||||
|
||||
// Apply the positioning
|
||||
tooltip.style.left = `${left}px`;
|
||||
tooltip.style.top = `${top}px`;
|
||||
|
||||
// Reset visibility state
|
||||
if (node.matches(':hover')) {
|
||||
tooltip.style.visibility = 'visible';
|
||||
tooltip.style.opacity = '1';
|
||||
}
|
||||
}
|
||||
|
||||
setupHoverEffects() {
|
||||
const dataNodes = document.querySelectorAll('.data-node');
|
||||
|
||||
dataNodes.forEach((node, index) => {
|
||||
node.addEventListener('mouseenter', () => {
|
||||
this.triggerHoverEffect(node);
|
||||
this.createRippleEffect(node);
|
||||
});
|
||||
|
||||
node.addEventListener('mouseleave', () => {
|
||||
this.resetHoverEffect(node);
|
||||
});
|
||||
});
|
||||
}
|
||||
triggerHoverEffect(node) {
|
||||
// Enhanced glow effect - using smaller scale to maintain scattered flow
|
||||
node.style.transform = 'scale(1.08)';
|
||||
node.style.zIndex = '1000';
|
||||
|
||||
// Add glow to node core
|
||||
const nodeCore = node.querySelector('.node-core');
|
||||
if (nodeCore) {
|
||||
nodeCore.style.boxShadow = '0 0 30px rgba(59, 130, 246, 0.8), 0 0 60px rgba(139, 92, 246, 0.6)';
|
||||
nodeCore.style.background = 'radial-gradient(circle, rgba(59, 130, 246, 0.4), rgba(139, 92, 246, 0.3))';
|
||||
}
|
||||
|
||||
// Enhance icon visibility
|
||||
const nodeIcon = node.querySelector('.node-icon img');
|
||||
if (nodeIcon) {
|
||||
nodeIcon.style.filter = 'brightness(1.2) saturate(1.3) drop-shadow(0 0 10px rgba(255, 255, 255, 0.8))';
|
||||
}
|
||||
|
||||
// Brighten label
|
||||
const nodeLabel = node.querySelector('.node-label');
|
||||
if (nodeLabel) {
|
||||
nodeLabel.style.color = '#ffffff';
|
||||
nodeLabel.style.textShadow = '0 0 10px rgba(59, 130, 246, 0.8)';
|
||||
nodeLabel.style.fontWeight = '600';
|
||||
}
|
||||
}
|
||||
|
||||
resetHoverEffect(node) {
|
||||
node.style.transform = '';
|
||||
node.style.zIndex = '';
|
||||
|
||||
const nodeCore = node.querySelector('.node-core');
|
||||
if (nodeCore) {
|
||||
nodeCore.style.boxShadow = '';
|
||||
nodeCore.style.background = '';
|
||||
}
|
||||
|
||||
const nodeIcon = node.querySelector('.node-icon img');
|
||||
if (nodeIcon) {
|
||||
nodeIcon.style.filter = '';
|
||||
}
|
||||
|
||||
const nodeLabel = node.querySelector('.node-label');
|
||||
if (nodeLabel) {
|
||||
nodeLabel.style.color = '';
|
||||
nodeLabel.style.textShadow = '';
|
||||
nodeLabel.style.fontWeight = '';
|
||||
}
|
||||
}
|
||||
|
||||
createRippleEffect(node) {
|
||||
const ripple = document.createElement('div');
|
||||
ripple.className = 'data-node-ripple';
|
||||
|
||||
const rect = node.getBoundingClientRect();
|
||||
const size = Math.max(rect.width, rect.height) * 2;
|
||||
|
||||
ripple.style.width = `${size}px`;
|
||||
ripple.style.height = `${size}px`;
|
||||
ripple.style.left = `${rect.width / 2 - size / 2}px`;
|
||||
ripple.style.top = `${rect.height / 2 - size / 2}px`;
|
||||
|
||||
node.appendChild(ripple);
|
||||
|
||||
// Remove ripple after animation
|
||||
setTimeout(() => {
|
||||
if (ripple.parentNode) {
|
||||
ripple.parentNode.removeChild(ripple);
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
|
||||
setupClickEffects() {
|
||||
const dataNodes = document.querySelectorAll('.data-node');
|
||||
|
||||
dataNodes.forEach(node => {
|
||||
node.addEventListener('click', () => {
|
||||
this.triggerClickEffect(node);
|
||||
this.createBurstEffect(node);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
triggerClickEffect(node) {
|
||||
// Create pulsating effect
|
||||
node.style.animation = 'none';
|
||||
setTimeout(() => {
|
||||
node.style.animation = 'nodeClickPulse 0.6s ease-out';
|
||||
}, 10);
|
||||
|
||||
// Reset animation after completion
|
||||
setTimeout(() => {
|
||||
node.style.animation = '';
|
||||
}, 600);
|
||||
}
|
||||
|
||||
createBurstEffect(node) {
|
||||
const particles = 8;
|
||||
const rect = node.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
|
||||
for (let i = 0; i < particles; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'data-node-particle';
|
||||
|
||||
const angle = (360 / particles) * i;
|
||||
const distance = 50 + Math.random() * 30;
|
||||
|
||||
const endX = centerX + Math.cos(angle * Math.PI / 180) * distance;
|
||||
const endY = centerY + Math.sin(angle * Math.PI / 180) * distance;
|
||||
|
||||
particle.style.left = `${centerX}px`;
|
||||
particle.style.top = `${centerY}px`;
|
||||
|
||||
document.body.appendChild(particle);
|
||||
|
||||
// Animate particle
|
||||
setTimeout(() => {
|
||||
particle.style.transform = `translate(${endX - centerX}px, ${endY - centerY}px)`;
|
||||
particle.style.opacity = '0';
|
||||
}, 10);
|
||||
|
||||
// Remove particle
|
||||
setTimeout(() => {
|
||||
if (particle.parentNode) {
|
||||
document.body.removeChild(particle);
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
}
|
||||
|
||||
addRequiredStyles() {
|
||||
const styles = document.createElement('style');
|
||||
styles.textContent = ` .data-node-tooltip {
|
||||
position: fixed;
|
||||
background: rgba(15, 31, 74, 0.95); /* Darker blue background */
|
||||
color: #ffffff;
|
||||
padding: 16px 20px; /* Increased padding */
|
||||
border-radius: 10px; /* More rounded corners */
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-width: 300px; /* Slightly wider */
|
||||
min-width: 220px;
|
||||
z-index: 100000; /* Ensure tooltip is above everything */
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: scale(0.95);
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); /* Smoother animation */
|
||||
backdrop-filter: blur(8px); /* Improved blur effect */
|
||||
border: 1px solid rgba(79, 150, 255, 0.4); /* Brighter border for contrast */
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(59, 130, 246, 0.3); /* Enhanced shadow */
|
||||
pointer-events: none;
|
||||
text-align: left; /* Left-aligned text for card style */
|
||||
transform-origin: center top;
|
||||
}
|
||||
|
||||
.tooltip-title {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem; /* Larger title */
|
||||
color: #ffffff;
|
||||
margin-bottom: 10px;
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid rgba(79, 150, 255, 0.3); /* Separator line */
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.tooltip-description {
|
||||
color: rgba(255, 255, 255, 0.9); /* Brighter text */
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Inter', sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: rgba(15, 31, 74, 0.95); /* Match tooltip background */
|
||||
border: 1px solid rgba(79, 150, 255, 0.4); /* Match tooltip border */
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.data-node-tooltip.tooltip-below .tooltip-arrow {
|
||||
bottom: auto;
|
||||
top: -6px;
|
||||
transform: translateX(-50%) rotate(-135deg);
|
||||
border: 1px solid rgba(79, 150, 255, 0.4); /* Match tooltip border */
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.data-node-ripple {
|
||||
position: absolute;
|
||||
border: 2px solid rgba(59, 130, 246, 0.6);
|
||||
border-radius: 50%;
|
||||
transform: scale(0);
|
||||
animation: dataRipple 0.6s ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes dataRipple {
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.data-node-particle {
|
||||
position: fixed;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: radial-gradient(circle, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.6));
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@keyframes nodeClickPulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.3); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.data-node {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.data-node:hover {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Enhanced neural network container interactions */
|
||||
.neural-network-container .data-node {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.neural-network-container .data-node:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* Enhanced hero animation data nodes */
|
||||
.hero-animation .data-node {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.hero-animation .data-node:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* Card tooltip animation */
|
||||
.data-node-tooltip {
|
||||
animation: tooltipPulse 3s ease-in-out infinite;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.data-node:hover .data-node-tooltip {
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes tooltipPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.7), 0 0 25px rgba(79, 150, 255, 0.5);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(styles);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a singleton instance to prevent duplicate initializations
|
||||
let dataNodeInteractionsInstance = null;
|
||||
|
||||
function initializeDataNodeInteractions() {
|
||||
if (!dataNodeInteractionsInstance) {
|
||||
dataNodeInteractionsInstance = new DataNodeInteractions();
|
||||
}
|
||||
return dataNodeInteractionsInstance;
|
||||
}
|
||||
|
||||
// Initialize data node interactions when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initializeDataNodeInteractions);
|
||||
|
||||
// Also initialize immediately if DOM is already loaded
|
||||
if (document.readyState !== 'loading') {
|
||||
initializeDataNodeInteractions();
|
||||
}
|
||||
|
||||
// Export for potential use in other modules
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = DataNodeInteractions;
|
||||
}
|
||||
755
js/data-node-interactions.js.new
Normal file
755
js/data-node-interactions.js.new
Normal file
@@ -0,0 +1,755 @@
|
||||
/**
|
||||
* Enhanced Unified Neural Network Animation and Data Node Interactive Effects
|
||||
* This file handles both the canvas background animation and data node interactions
|
||||
* with advanced interactive elements and dynamic visual effects
|
||||
*/
|
||||
|
||||
// Neural Network Canvas Animation Class
|
||||
class NeuralNetworkAnimation {
|
||||
constructor() {
|
||||
this.canvas = document.querySelector('.mesh-canvas');
|
||||
this.ctx = this.canvas ? this.canvas.getContext('2d') : null;
|
||||
this.nodes = [];
|
||||
this.connections = [];
|
||||
this.mousePos = { x: 0, y: 0 };
|
||||
this.animationId = null;
|
||||
|
||||
if (this.ctx) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupCanvas();
|
||||
this.createNodes();
|
||||
this.setupInteractions();
|
||||
this.setupResize();
|
||||
this.animate();
|
||||
}
|
||||
|
||||
setupCanvas() {
|
||||
const heroSection = document.getElementById('hero');
|
||||
if (heroSection) {
|
||||
const rect = heroSection.getBoundingClientRect();
|
||||
this.canvas.width = rect.width;
|
||||
this.canvas.height = rect.height;
|
||||
|
||||
this.canvas.style.width = '100%';
|
||||
this.canvas.style.height = '100%';
|
||||
}
|
||||
}
|
||||
|
||||
setupResize() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.setupCanvas();
|
||||
this.nodes.forEach(node => {
|
||||
node.x = Math.min(node.x, this.canvas.width);
|
||||
node.y = Math.min(node.y, this.canvas.height);
|
||||
});
|
||||
});
|
||||
}
|
||||
createNodes() {
|
||||
const nodeCount = 40;
|
||||
this.nodes = [];
|
||||
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
this.nodes.push({
|
||||
x: Math.random() * this.canvas.width,
|
||||
y: Math.random() * this.canvas.height,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
radius: Math.random() * 3 + 1,
|
||||
opacity: Math.random() * 0.5 + 0.2,
|
||||
pulsePhase: Math.random() * Math.PI * 2,
|
||||
energy: Math.random()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateNodes() {
|
||||
this.nodes.forEach(node => {
|
||||
// Update position
|
||||
node.x += node.vx;
|
||||
node.y += node.vy;
|
||||
|
||||
// Bounce off edges
|
||||
if (node.x <= 0 || node.x >= this.canvas.width) node.vx *= -1;
|
||||
if (node.y <= 0 || node.y >= this.canvas.height) node.vy *= -1;
|
||||
|
||||
// Keep nodes in bounds
|
||||
node.x = Math.max(0, Math.min(this.canvas.width, node.x));
|
||||
node.y = Math.max(0, Math.min(this.canvas.height, node.y));
|
||||
|
||||
// Update pulse
|
||||
node.pulsePhase += 0.02;
|
||||
node.energy = 0.5 + 0.5 * Math.sin(node.pulsePhase);
|
||||
});
|
||||
}
|
||||
|
||||
drawNodes() {
|
||||
this.nodes.forEach(node => {
|
||||
const gradient = this.ctx.createRadialGradient(
|
||||
node.x, node.y, 0,
|
||||
node.x, node.y, node.radius * 3
|
||||
);
|
||||
|
||||
const alpha = node.opacity * node.energy;
|
||||
gradient.addColorStop(0, `rgba(59, 130, 246, ${alpha})`);
|
||||
gradient.addColorStop(0.5, `rgba(139, 92, 246, ${alpha * 0.7})`);
|
||||
gradient.addColorStop(1, `rgba(236, 72, 153, 0)`);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
|
||||
// Core dot
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(node.x, node.y, node.radius * 0.3, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.8})`;
|
||||
this.ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
drawConnections() {
|
||||
const maxDistance = 120;
|
||||
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
for (let j = i + 1; j < this.nodes.length; j++) {
|
||||
const nodeA = this.nodes[i];
|
||||
const nodeB = this.nodes[j];
|
||||
|
||||
const dx = nodeB.x - nodeA.x;
|
||||
const dy = nodeB.y - nodeA.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < maxDistance) {
|
||||
const opacity = (1 - distance / maxDistance) * 0.3;
|
||||
const energyBoost = (nodeA.energy + nodeB.energy) / 2;
|
||||
|
||||
// Create dynamic gradient line
|
||||
const gradient = this.ctx.createLinearGradient(
|
||||
nodeA.x, nodeA.y, nodeB.x, nodeB.y
|
||||
);
|
||||
|
||||
gradient.addColorStop(0, `rgba(59, 130, 246, ${opacity * energyBoost})`);
|
||||
gradient.addColorStop(0.5, `rgba(139, 92, 246, ${opacity * energyBoost * 0.8})`);
|
||||
gradient.addColorStop(1, `rgba(236, 72, 153, ${opacity * energyBoost * 0.6})`);
|
||||
|
||||
this.ctx.strokeStyle = gradient;
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.globalCompositeOperation = 'lighter';
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(nodeA.x, nodeA.y);
|
||||
this.ctx.lineTo(nodeB.x, nodeB.y);
|
||||
this.ctx.stroke();
|
||||
|
||||
this.ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// Data pulse along connection
|
||||
if (Math.random() < 0.01) {
|
||||
this.createDataPulse(nodeA, nodeB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createDataPulse(nodeA, nodeB) {
|
||||
const pulse = {
|
||||
startX: nodeA.x,
|
||||
startY: nodeA.y,
|
||||
endX: nodeB.x,
|
||||
endY: nodeB.y,
|
||||
progress: 0,
|
||||
speed: 0.02,
|
||||
life: 1
|
||||
};
|
||||
|
||||
this.dataPulses = this.dataPulses || [];
|
||||
this.dataPulses.push(pulse);
|
||||
}
|
||||
|
||||
updateDataPulses() {
|
||||
if (!this.dataPulses) return;
|
||||
|
||||
this.dataPulses = this.dataPulses.filter(pulse => {
|
||||
pulse.progress += pulse.speed;
|
||||
pulse.life -= 0.01;
|
||||
|
||||
if (pulse.progress >= 1 || pulse.life <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Draw pulse
|
||||
const x = pulse.startX + (pulse.endX - pulse.startX) * pulse.progress;
|
||||
const y = pulse.startY + (pulse.endY - pulse.startY) * pulse.progress;
|
||||
|
||||
const gradient = this.ctx.createRadialGradient(x, y, 0, x, y, 6);
|
||||
gradient.addColorStop(0, `rgba(255, 255, 255, ${pulse.life})`);
|
||||
gradient.addColorStop(1, `rgba(59, 130, 246, 0)`);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(x, y, 4, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseMove(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
this.mousePos.x = e.clientX - rect.left;
|
||||
this.mousePos.y = e.clientY - rect.top;
|
||||
|
||||
// Create attraction effect
|
||||
this.nodes.forEach(node => {
|
||||
const dx = this.mousePos.x - node.x;
|
||||
const dy = this.mousePos.y - node.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
const force = (100 - distance) / 100 * 0.01;
|
||||
node.vx += dx * force;
|
||||
node.vy += dy * force;
|
||||
node.energy = Math.min(1, node.energy + 0.1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animate() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Add subtle background gradient
|
||||
const bgGradient = this.ctx.createRadialGradient(
|
||||
this.canvas.width / 2, this.canvas.height / 2, 0,
|
||||
this.canvas.width / 2, this.canvas.height / 2, Math.max(this.canvas.width, this.canvas.height) / 2
|
||||
);
|
||||
bgGradient.addColorStop(0, 'rgba(59, 130, 246, 0.02)');
|
||||
bgGradient.addColorStop(1, 'rgba(139, 92, 246, 0.01)');
|
||||
|
||||
this.ctx.fillStyle = bgGradient;
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.updateNodes();
|
||||
this.drawConnections();
|
||||
this.drawNodes();
|
||||
this.updateDataPulses();
|
||||
|
||||
this.animationId = requestAnimationFrame(() => this.animate());
|
||||
}
|
||||
|
||||
setupInteractions() {
|
||||
// Mouse interaction only - data node interactions handled by DataNodeInteractions class
|
||||
this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data Node Interactive Effects Class
|
||||
class DataNodeInteractions {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
init() {
|
||||
// Wait for DOM to be fully loaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
this.initializeInteractions();
|
||||
});
|
||||
} else {
|
||||
this.initializeInteractions();
|
||||
}
|
||||
}
|
||||
initializeInteractions() {
|
||||
this.setupTooltips();
|
||||
this.setupHoverEffects();
|
||||
this.setupClickEffects();
|
||||
this.addRequiredStyles();
|
||||
}
|
||||
|
||||
setupTooltips() {
|
||||
const dataNodes = document.querySelectorAll('.data-node');
|
||||
|
||||
dataNodes.forEach((node, index) => {
|
||||
// Create tooltip element
|
||||
const tooltip = this.createTooltip(node);
|
||||
document.body.appendChild(tooltip); // Append to body instead of node for better positioning
|
||||
|
||||
// Store reference to the tooltip on the node
|
||||
node.tooltip = tooltip;
|
||||
|
||||
// Add hover events for tooltip
|
||||
node.addEventListener('mouseenter', (e) => {
|
||||
this.showTooltip(tooltip, node, e);
|
||||
this.triggerHoverEffect(node);
|
||||
});
|
||||
|
||||
node.addEventListener('mouseleave', () => {
|
||||
this.hideTooltip(tooltip);
|
||||
this.resetHoverEffect(node);
|
||||
});
|
||||
|
||||
// Update tooltip position on mouse move - only if needed
|
||||
node.addEventListener('mousemove', (e) => {
|
||||
// Only update on significant mouse movement to prevent jitter
|
||||
if (!node.lastMouseX || Math.abs(e.clientX - node.lastMouseX) > 5 ||
|
||||
!node.lastMouseY || Math.abs(e.clientY - node.lastMouseY) > 5) {
|
||||
this.updateTooltipPosition(tooltip, node, e);
|
||||
node.lastMouseX = e.clientX;
|
||||
node.lastMouseY = e.clientY;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createTooltip(node) {
|
||||
const techType = node.getAttribute('data-tech');
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'data-node-tooltip';
|
||||
|
||||
// Enhanced concise descriptions for each technology
|
||||
const descriptions = {
|
||||
'Machine Learning': 'Building intelligent systems that learn from data to predict outcomes and automate decisions.',
|
||||
'Generative AI': 'Creating novel content from text and images to code, unlocking new creative possibilities.',
|
||||
'Data Analytics': 'Transforming raw data into actionable business insights to drive strategic decisions.',
|
||||
'Automation': 'Designing intelligent workflows that increase efficiency and free up human resources.',
|
||||
'Finance': 'Using AI for risk assessment, fraud detection, and optimized investment strategies.',
|
||||
'Healthcare': 'Enabling faster diagnoses, personalized treatments, and improved patient care.',
|
||||
'Retail': 'Powering personalized shopping experiences, inventory optimization, and demand forecasting.',
|
||||
'Manufacturing': 'Enhancing production efficiency, quality control, and supply chain optimization.',
|
||||
'Agentic AI': 'Developing autonomous AI agents that perceive environments and take goal-driven actions.',
|
||||
'Marketing': 'Creating data-driven campaigns, customer segmentation, and conversion optimization.',
|
||||
'Media & Entertainment': 'Enabling content creation, audience engagement, and personalized recommendations.',
|
||||
'Travel': 'Implementing smart pricing strategies and enhancing customer travel experiences.',
|
||||
'Education': 'Delivering personalized learning paths and educational content innovation.',
|
||||
'Technology': 'Providing cutting-edge AI development and integration services for tech companies.'
|
||||
};
|
||||
|
||||
tooltip.innerHTML = `
|
||||
<div class="tooltip-title">${techType}</div>
|
||||
<div class="tooltip-description">${descriptions[techType] || 'AI-powered solutions and innovations for business transformation.'}</div>
|
||||
<div class="tooltip-arrow"></div>
|
||||
`;
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
showTooltip(tooltip, node, event) {
|
||||
// Reset any ongoing transitions with enhanced animation
|
||||
tooltip.style.transition = 'all 0.3s cubic-bezier(0.2, 0, 0.2, 1)'; // Smoother easing
|
||||
tooltip.style.opacity = '1';
|
||||
tooltip.style.visibility = 'visible';
|
||||
tooltip.style.transform = 'translate(-50%, 0) scale(1)';
|
||||
|
||||
// Add a subtle animation highlight
|
||||
setTimeout(() => {
|
||||
tooltip.style.boxShadow = '0 12px 35px rgba(0, 0, 0, 0.7), 0 0 20px rgba(79, 150, 255, 0.4)';
|
||||
}, 50);
|
||||
|
||||
this.updateTooltipPosition(tooltip, node, event);
|
||||
|
||||
// Hide any other visible tooltips
|
||||
document.querySelectorAll('.data-node-tooltip').forEach(otherTooltip => {
|
||||
if (otherTooltip !== tooltip && otherTooltip.style.visibility === 'visible') {
|
||||
otherTooltip.style.opacity = '0';
|
||||
otherTooltip.style.visibility = 'hidden';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hideTooltip(tooltip) {
|
||||
// Smooth fade out
|
||||
tooltip.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 1, 1)';
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.visibility = 'hidden';
|
||||
tooltip.style.transform = 'translate(-50%, 12px) scale(0.95)';
|
||||
tooltip.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(59, 130, 246, 0.3)';
|
||||
}
|
||||
|
||||
updateTooltipPosition(tooltip, node, event) {
|
||||
const rect = node.getBoundingClientRect();
|
||||
|
||||
// Ensure the tooltip has proper dimensions for calculation
|
||||
tooltip.style.visibility = 'hidden';
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.display = 'block';
|
||||
|
||||
const tooltipRect = tooltip.getBoundingClientRect();
|
||||
|
||||
// Position tooltip above the node with increased distance for card style
|
||||
let left = rect.left + (rect.width / 2);
|
||||
let top = rect.top - tooltipRect.height - 20; // Distance above node
|
||||
|
||||
// Adjust if tooltip goes off screen
|
||||
const viewport = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
|
||||
// Check if we're on the right side of the screen (possibly overlapping hero content)
|
||||
const isRightSide = rect.left > (viewport.width / 2);
|
||||
|
||||
// Position based on side of screen for better readability
|
||||
if (isRightSide) {
|
||||
// For nodes on right side, align to the right edge of the node
|
||||
left = rect.right - tooltipRect.width/2;
|
||||
} else {
|
||||
// For left side nodes, align to the left edge of the node
|
||||
left = rect.left + tooltipRect.width/2;
|
||||
}
|
||||
|
||||
// Ensure tooltip stays within viewport bounds with margins
|
||||
const margin = 25;
|
||||
const maxLeft = viewport.width - tooltipRect.width - margin;
|
||||
const minLeft = margin + tooltipRect.width/2;
|
||||
|
||||
left = Math.max(minLeft, Math.min(left, maxLeft));
|
||||
|
||||
// If not enough room above, show below
|
||||
if (top < margin) {
|
||||
top = rect.bottom + 20; // Place below with distance
|
||||
tooltip.classList.add('tooltip-below');
|
||||
} else {
|
||||
tooltip.classList.remove('tooltip-below');
|
||||
}
|
||||
|
||||
// Apply the positioning
|
||||
tooltip.style.left = `${left}px`;
|
||||
tooltip.style.top = `${top}px`;
|
||||
|
||||
// Reset visibility state to what it should be (it was set to hidden for calculations)
|
||||
if (node.matches(':hover')) {
|
||||
tooltip.style.visibility = 'visible';
|
||||
tooltip.style.opacity = '1';
|
||||
}
|
||||
}
|
||||
|
||||
setupHoverEffects() {
|
||||
const dataNodes = document.querySelectorAll('.data-node');
|
||||
|
||||
dataNodes.forEach((node, index) => {
|
||||
node.addEventListener('mouseenter', () => {
|
||||
this.triggerHoverEffect(node);
|
||||
this.createRippleEffect(node);
|
||||
});
|
||||
|
||||
node.addEventListener('mouseleave', () => {
|
||||
this.resetHoverEffect(node);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
triggerHoverEffect(node) {
|
||||
// Enhanced glow effect
|
||||
node.style.transform = 'scale(1.15)';
|
||||
node.style.zIndex = '1000';
|
||||
|
||||
// Add glow to node core
|
||||
const nodeCore = node.querySelector('.node-core');
|
||||
if (nodeCore) {
|
||||
nodeCore.style.boxShadow = '0 0 30px rgba(59, 130, 246, 0.8), 0 0 60px rgba(139, 92, 246, 0.6)';
|
||||
nodeCore.style.background = 'radial-gradient(circle, rgba(59, 130, 246, 0.4), rgba(139, 92, 246, 0.3))';
|
||||
}
|
||||
|
||||
// Enhance icon visibility
|
||||
const nodeIcon = node.querySelector('.node-icon img');
|
||||
if (nodeIcon) {
|
||||
nodeIcon.style.filter = 'brightness(1.2) saturate(1.3) drop-shadow(0 0 10px rgba(255, 255, 255, 0.8))';
|
||||
}
|
||||
|
||||
// Brighten label
|
||||
const nodeLabel = node.querySelector('.node-label');
|
||||
if (nodeLabel) {
|
||||
nodeLabel.style.color = '#ffffff';
|
||||
nodeLabel.style.textShadow = '0 0 10px rgba(59, 130, 246, 0.8)';
|
||||
nodeLabel.style.fontWeight = '600';
|
||||
}
|
||||
}
|
||||
|
||||
resetHoverEffect(node) {
|
||||
node.style.transform = '';
|
||||
node.style.zIndex = '';
|
||||
|
||||
const nodeCore = node.querySelector('.node-core');
|
||||
if (nodeCore) {
|
||||
nodeCore.style.boxShadow = '';
|
||||
nodeCore.style.background = '';
|
||||
}
|
||||
|
||||
const nodeIcon = node.querySelector('.node-icon img');
|
||||
if (nodeIcon) {
|
||||
nodeIcon.style.filter = '';
|
||||
}
|
||||
|
||||
const nodeLabel = node.querySelector('.node-label');
|
||||
if (nodeLabel) {
|
||||
nodeLabel.style.color = '';
|
||||
nodeLabel.style.textShadow = '';
|
||||
nodeLabel.style.fontWeight = '';
|
||||
}
|
||||
}
|
||||
|
||||
createRippleEffect(node) {
|
||||
const ripple = document.createElement('div');
|
||||
ripple.className = 'data-node-ripple';
|
||||
|
||||
const rect = node.getBoundingClientRect();
|
||||
const size = Math.max(rect.width, rect.height) * 2;
|
||||
|
||||
ripple.style.width = `${size}px`;
|
||||
ripple.style.height = `${size}px`;
|
||||
ripple.style.left = `${rect.width / 2 - size / 2}px`;
|
||||
ripple.style.top = `${rect.height / 2 - size / 2}px`;
|
||||
|
||||
node.appendChild(ripple);
|
||||
|
||||
// Remove ripple after animation
|
||||
setTimeout(() => {
|
||||
if (ripple.parentNode) {
|
||||
ripple.parentNode.removeChild(ripple);
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
|
||||
setupClickEffects() {
|
||||
const dataNodes = document.querySelectorAll('.data-node');
|
||||
|
||||
dataNodes.forEach(node => {
|
||||
node.addEventListener('click', () => {
|
||||
this.triggerClickEffect(node);
|
||||
this.createBurstEffect(node);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
triggerClickEffect(node) {
|
||||
// Create pulsating effect
|
||||
node.style.animation = 'none';
|
||||
setTimeout(() => {
|
||||
node.style.animation = 'nodeClickPulse 0.6s ease-out';
|
||||
}, 10);
|
||||
|
||||
// Reset animation after completion
|
||||
setTimeout(() => {
|
||||
node.style.animation = '';
|
||||
}, 600);
|
||||
}
|
||||
|
||||
createBurstEffect(node) {
|
||||
const particles = 8;
|
||||
const rect = node.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
|
||||
for (let i = 0; i < particles; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'data-node-particle';
|
||||
|
||||
const angle = (360 / particles) * i;
|
||||
const distance = 50 + Math.random() * 30;
|
||||
|
||||
const endX = centerX + Math.cos(angle * Math.PI / 180) * distance;
|
||||
const endY = centerY + Math.sin(angle * Math.PI / 180) * distance;
|
||||
|
||||
particle.style.left = `${centerX}px`;
|
||||
particle.style.top = `${centerY}px`;
|
||||
|
||||
document.body.appendChild(particle);
|
||||
|
||||
// Animate particle
|
||||
setTimeout(() => {
|
||||
particle.style.transform = `translate(${endX - centerX}px, ${endY - centerY}px)`;
|
||||
particle.style.opacity = '0';
|
||||
}, 10);
|
||||
|
||||
// Remove particle
|
||||
setTimeout(() => {
|
||||
if (particle.parentNode) {
|
||||
document.body.removeChild(particle);
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
}
|
||||
|
||||
addRequiredStyles() {
|
||||
const styles = document.createElement('style');
|
||||
styles.textContent = `
|
||||
.data-node-tooltip {
|
||||
position: fixed;
|
||||
background: rgba(15, 31, 74, 0.95); /* Darker blue background */
|
||||
color: #ffffff;
|
||||
padding: 16px 20px; /* Increased padding */
|
||||
border-radius: 10px; /* More rounded corners */
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-width: 300px; /* Slightly wider */
|
||||
min-width: 220px;
|
||||
z-index: 50000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(0) scale(0.95);
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); /* Smoother animation */
|
||||
backdrop-filter: blur(8px); /* Improved blur effect */
|
||||
border: 1px solid rgba(79, 150, 255, 0.4); /* Brighter border for contrast */
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(59, 130, 246, 0.3); /* Enhanced shadow */
|
||||
pointer-events: none;
|
||||
text-align: left; /* Left-aligned text for card style */
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
.tooltip-title {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem; /* Larger title */
|
||||
color: #ffffff;
|
||||
margin-bottom: 10px;
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid rgba(79, 150, 255, 0.3); /* Separator line */
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.tooltip-description {
|
||||
color: rgba(255, 255, 255, 0.9); /* Brighter text */
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Inter', sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: rgba(15, 31, 74, 0.95); /* Match tooltip background */
|
||||
border: 1px solid rgba(79, 150, 255, 0.4); /* Match tooltip border */
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.data-node-tooltip.tooltip-below .tooltip-arrow {
|
||||
bottom: auto;
|
||||
top: -6px;
|
||||
transform: translateX(-50%) rotate(-135deg);
|
||||
border: 1px solid rgba(79, 150, 255, 0.4); /* Match tooltip border */
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.data-node-ripple {
|
||||
position: absolute;
|
||||
border: 2px solid rgba(59, 130, 246, 0.6);
|
||||
border-radius: 50%;
|
||||
transform: scale(0);
|
||||
animation: dataRipple 0.6s ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes dataRipple {
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.data-node-particle {
|
||||
position: fixed;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: radial-gradient(circle, rgba(59, 130, 246, 0.8), rgba(139, 92, 246, 0.6));
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@keyframes nodeClickPulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.3); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.data-node {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.data-node:hover {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Enhanced neural network container interactions */
|
||||
.neural-network-container .data-node {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.neural-network-container .data-node:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* Enhanced hero animation data nodes */
|
||||
.hero-animation .data-node {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.hero-animation .data-node:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* Card tooltip animation */
|
||||
.data-node-tooltip {
|
||||
animation: tooltipPulse 3s ease-in-out infinite;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.data-node:hover .data-node-tooltip {
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes tooltipPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.7), 0 0 25px rgba(79, 150, 255, 0.5);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(styles);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize data node interactions when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new DataNodeInteractions();
|
||||
});
|
||||
|
||||
// Initialize when script loads
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new DataNodeInteractions();
|
||||
});
|
||||
|
||||
// Also initialize immediately if DOM is already loaded
|
||||
if (document.readyState === 'loading') {
|
||||
// Document is still loading, wait for DOMContentLoaded
|
||||
} else {
|
||||
// Document has already loaded
|
||||
new DataNodeInteractions();
|
||||
}
|
||||
|
||||
// Export for potential use in other modules
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = DataNodeInteractions;
|
||||
}
|
||||
364
js/dynamic-content.js
Normal file
364
js/dynamic-content.js
Normal file
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* Dynamic Content Loader
|
||||
* Handles lazy loading of content, interactive cards, and tabs
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize lazy loading for images
|
||||
initLazyLoading();
|
||||
|
||||
// Initialize interactive cards
|
||||
initInteractiveCards();
|
||||
|
||||
// Initialize tabs if they exist
|
||||
initTabSystem();
|
||||
|
||||
// Initialize accordion components
|
||||
initAccordion();
|
||||
|
||||
// Initialize dynamic counters
|
||||
initCounters();
|
||||
|
||||
// Initialize dynamic content filter
|
||||
initContentFilter();
|
||||
});
|
||||
|
||||
// Lazy load images as they enter viewport
|
||||
function initLazyLoading() {
|
||||
const lazyImages = document.querySelectorAll('.lazy-image');
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
const imageObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
|
||||
// Replace src with data-src
|
||||
if (img.dataset.src) {
|
||||
img.src = img.dataset.src;
|
||||
|
||||
// Add fade-in effect
|
||||
img.classList.add('image-loaded');
|
||||
}
|
||||
|
||||
// Stop observing after loading
|
||||
imageObserver.unobserve(img);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '50px',
|
||||
threshold: 0.1
|
||||
});
|
||||
|
||||
// Observe all lazy images
|
||||
lazyImages.forEach(img => {
|
||||
imageObserver.observe(img);
|
||||
});
|
||||
} else {
|
||||
// Fallback for browsers without IntersectionObserver
|
||||
lazyImages.forEach(img => {
|
||||
if (img.dataset.src) {
|
||||
img.src = img.dataset.src;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Interactive hover and click effects for cards
|
||||
function initInteractiveCards() {
|
||||
const cards = document.querySelectorAll('.card, .service-card, .exp-item, .team-member');
|
||||
|
||||
cards.forEach(card => {
|
||||
// 3D tilt effect on mouse move
|
||||
card.addEventListener('mousemove', function(e) {
|
||||
const rect = this.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
// Calculate tilt based on mouse position
|
||||
const centerX = rect.width / 2;
|
||||
const centerY = rect.height / 2;
|
||||
|
||||
const tiltX = (centerY - y) / 20;
|
||||
const tiltY = (x - centerX) / 20;
|
||||
|
||||
// Apply 3D transform
|
||||
this.style.transform = `perspective(1000px) rotateX(${tiltX}deg) rotateY(${tiltY}deg) scale3d(1.02, 1.02, 1.02)`;
|
||||
|
||||
// Add highlight effect to follow cursor
|
||||
const highlight = this.querySelector('.card-highlight') || document.createElement('div');
|
||||
if (!this.querySelector('.card-highlight')) {
|
||||
highlight.className = 'card-highlight';
|
||||
this.appendChild(highlight);
|
||||
}
|
||||
|
||||
highlight.style.opacity = '1';
|
||||
highlight.style.transform = `translate(${x}px, ${y}px)`;
|
||||
});
|
||||
|
||||
// Reset on mouse leave
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.transform = '';
|
||||
const highlight = this.querySelector('.card-highlight');
|
||||
if (highlight) {
|
||||
highlight.style.opacity = '0';
|
||||
}
|
||||
});
|
||||
|
||||
// Click effect
|
||||
card.addEventListener('click', function() {
|
||||
this.classList.add('card-clicked');
|
||||
setTimeout(() => {
|
||||
this.classList.remove('card-clicked');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Tab system for content switching
|
||||
function initTabSystem() {
|
||||
const tabContainers = document.querySelectorAll('.tab-container');
|
||||
|
||||
tabContainers.forEach(container => {
|
||||
const tabsNav = container.querySelector('.tabs-nav');
|
||||
const tabsContent = container.querySelector('.tabs-content');
|
||||
|
||||
if (!tabsNav || !tabsContent) return;
|
||||
|
||||
const tabs = tabsNav.querySelectorAll('.tab');
|
||||
const tabPanels = tabsContent.querySelectorAll('.tab-panel');
|
||||
|
||||
// Initialize first tab as active
|
||||
if (tabs.length > 0 && tabPanels.length > 0) {
|
||||
tabs[0].classList.add('active-tab');
|
||||
tabPanels[0].classList.add('active-panel');
|
||||
}
|
||||
|
||||
// Add click handlers to tabs
|
||||
tabs.forEach((tab, index) => {
|
||||
tab.addEventListener('click', function() {
|
||||
// Remove active class from all tabs and panels
|
||||
tabs.forEach(t => t.classList.remove('active-tab'));
|
||||
tabPanels.forEach(panel => panel.classList.remove('active-panel'));
|
||||
|
||||
// Add active class to current tab and panel
|
||||
this.classList.add('active-tab');
|
||||
if (tabPanels[index]) {
|
||||
tabPanels[index].classList.add('active-panel');
|
||||
}
|
||||
|
||||
// Add entrance animation for tab content
|
||||
if (tabPanels[index]) {
|
||||
tabPanels[index].classList.add('panel-entering');
|
||||
setTimeout(() => {
|
||||
tabPanels[index].classList.remove('panel-entering');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Show active tab indicator
|
||||
const indicator = tabsNav.querySelector('.tab-indicator') || document.createElement('div');
|
||||
if (!tabsNav.querySelector('.tab-indicator')) {
|
||||
indicator.className = 'tab-indicator';
|
||||
tabsNav.appendChild(indicator);
|
||||
}
|
||||
|
||||
// Position indicator below active tab
|
||||
const tabRect = this.getBoundingClientRect();
|
||||
const navRect = tabsNav.getBoundingClientRect();
|
||||
indicator.style.width = `${tabRect.width}px`;
|
||||
indicator.style.left = `${tabRect.left - navRect.left}px`;
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger indicator positioning for first tab
|
||||
if (tabs[0]) {
|
||||
setTimeout(() => {
|
||||
tabs[0].click();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Accordion functionality for FAQ or content sections
|
||||
function initAccordion() {
|
||||
const accordions = document.querySelectorAll('.accordion');
|
||||
|
||||
accordions.forEach(accordion => {
|
||||
const items = accordion.querySelectorAll('.accordion-item');
|
||||
|
||||
items.forEach(item => {
|
||||
const header = item.querySelector('.accordion-header');
|
||||
const content = item.querySelector('.accordion-content');
|
||||
|
||||
if (!header || !content) return;
|
||||
|
||||
// Set initial state
|
||||
content.style.maxHeight = '0px';
|
||||
content.style.overflow = 'hidden';
|
||||
content.style.transition = 'max-height 0.3s ease';
|
||||
|
||||
// Toggle accordion on click
|
||||
header.addEventListener('click', function() {
|
||||
// Check if this item is already open
|
||||
const isOpen = item.classList.contains('accordion-open');
|
||||
|
||||
// Close all items
|
||||
items.forEach(otherItem => {
|
||||
otherItem.classList.remove('accordion-open');
|
||||
const otherContent = otherItem.querySelector('.accordion-content');
|
||||
if (otherContent) {
|
||||
otherContent.style.maxHeight = '0px';
|
||||
}
|
||||
});
|
||||
|
||||
// Open this item if it wasn't already open
|
||||
if (!isOpen) {
|
||||
item.classList.add('accordion-open');
|
||||
content.style.maxHeight = `${content.scrollHeight}px`;
|
||||
}
|
||||
|
||||
// Add indicator animation
|
||||
const indicator = header.querySelector('.accordion-indicator') || document.createElement('div');
|
||||
if (!header.querySelector('.accordion-indicator')) {
|
||||
indicator.className = 'accordion-indicator';
|
||||
indicator.innerHTML = '<span>+</span>';
|
||||
header.appendChild(indicator);
|
||||
}
|
||||
|
||||
// Update indicator
|
||||
indicator.innerHTML = item.classList.contains('accordion-open') ? '<span>−</span>' : '<span>+</span>';
|
||||
});
|
||||
});
|
||||
|
||||
// Open first item by default if specified
|
||||
if (accordion.classList.contains('first-open') && items[0]) {
|
||||
setTimeout(() => {
|
||||
items[0].querySelector('.accordion-header').click();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Animated counters that increment when in view
|
||||
function initCounters() {
|
||||
const counters = document.querySelectorAll('.counter');
|
||||
|
||||
if (!counters.length) return;
|
||||
|
||||
const counterObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const counter = entry.target;
|
||||
const targetValue = parseInt(counter.getAttribute('data-target'));
|
||||
const duration = parseInt(counter.getAttribute('data-duration')) || 2000;
|
||||
|
||||
// Prevent multiple animations
|
||||
if (counter.classList.contains('counting')) return;
|
||||
counter.classList.add('counting');
|
||||
|
||||
animateCounter(counter, targetValue, duration);
|
||||
|
||||
// Stop observing after animation
|
||||
counterObserver.unobserve(counter);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.2
|
||||
});
|
||||
|
||||
// Observe all counters
|
||||
counters.forEach(counter => {
|
||||
counterObserver.observe(counter);
|
||||
});
|
||||
|
||||
// Counter animation function
|
||||
function animateCounter(counter, target, duration) {
|
||||
let startTime = null;
|
||||
const startValue = 0;
|
||||
|
||||
function step(timestamp) {
|
||||
if (!startTime) startTime = timestamp;
|
||||
|
||||
const progress = Math.min((timestamp - startTime) / duration, 1);
|
||||
const currentValue = Math.floor(progress * (target - startValue) + startValue);
|
||||
|
||||
counter.textContent = formatNumber(currentValue);
|
||||
|
||||
if (progress < 1) {
|
||||
window.requestAnimationFrame(step);
|
||||
} else {
|
||||
counter.textContent = formatNumber(target);
|
||||
counter.classList.add('counter-complete');
|
||||
}
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
// Format number with commas
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
}
|
||||
}
|
||||
|
||||
// Content filtering system for portfolios or galleries
|
||||
function initContentFilter() {
|
||||
const filterContainers = document.querySelectorAll('.filter-container');
|
||||
|
||||
filterContainers.forEach(container => {
|
||||
const filterButtons = container.querySelectorAll('.filter-button');
|
||||
const filterItems = container.querySelectorAll('.filter-item');
|
||||
|
||||
if (!filterButtons.length || !filterItems.length) return;
|
||||
|
||||
// Click handler for filter buttons
|
||||
filterButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
// Remove active class from all buttons
|
||||
filterButtons.forEach(btn => {
|
||||
btn.classList.remove('active-filter');
|
||||
});
|
||||
|
||||
// Add active class to clicked button
|
||||
this.classList.add('active-filter');
|
||||
|
||||
// Get filter category
|
||||
const filterCategory = this.getAttribute('data-filter');
|
||||
|
||||
// Filter items
|
||||
filterItems.forEach(item => {
|
||||
const itemCategories = item.getAttribute('data-categories');
|
||||
|
||||
if (!itemCategories) return;
|
||||
|
||||
// Check if item matches filter
|
||||
if (filterCategory === 'all' || itemCategories.includes(filterCategory)) {
|
||||
// Show item with animation
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'scale(0.8)';
|
||||
item.style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
item.style.opacity = '1';
|
||||
item.style.transform = 'scale(1)';
|
||||
}, 50);
|
||||
} else {
|
||||
// Hide item with animation
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'scale(0.8)';
|
||||
|
||||
setTimeout(() => {
|
||||
item.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Set "All" filter as active by default
|
||||
const allFilterButton = container.querySelector('.filter-button[data-filter="all"]');
|
||||
if (allFilterButton) {
|
||||
allFilterButton.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
311
js/form-handler.js
Normal file
311
js/form-handler.js
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Form validation and submission handler
|
||||
* Provides interactive form experiences with validation and feedback
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all forms on the page
|
||||
const forms = document.querySelectorAll('form');
|
||||
|
||||
forms.forEach(form => {
|
||||
initFormValidation(form);
|
||||
});
|
||||
|
||||
// Contact form special handling
|
||||
const contactForm = document.querySelector('.contact-form');
|
||||
if (contactForm) {
|
||||
initContactForm(contactForm);
|
||||
}
|
||||
|
||||
// Newsletter form special handling
|
||||
const newsletterForm = document.querySelector('.newsletter-form');
|
||||
if (newsletterForm) {
|
||||
initNewsletterForm(newsletterForm);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize form validation
|
||||
function initFormValidation(form) {
|
||||
const inputs = form.querySelectorAll('input, textarea, select');
|
||||
|
||||
// Add validation classes and event listeners
|
||||
inputs.forEach(input => {
|
||||
// Skip submit buttons
|
||||
if (input.type === 'submit') return;
|
||||
|
||||
// Create validation message element
|
||||
const validationMessage = document.createElement('div');
|
||||
validationMessage.className = 'validation-message';
|
||||
input.parentNode.appendChild(validationMessage);
|
||||
|
||||
// Add input event listeners
|
||||
input.addEventListener('blur', function() {
|
||||
validateInput(input);
|
||||
});
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
// Remove error styling on input
|
||||
input.classList.remove('input-error');
|
||||
validationMessage.textContent = '';
|
||||
});
|
||||
|
||||
// Add floating label effect for text inputs
|
||||
if (['text', 'email', 'tel', 'password', 'textarea'].includes(input.type)) {
|
||||
const label = document.querySelector(`label[for="${input.id}"]`);
|
||||
|
||||
if (label) {
|
||||
// Position the label over the input initially
|
||||
label.classList.add('floating-label');
|
||||
|
||||
// Move label up when input is focused or has content
|
||||
input.addEventListener('focus', function() {
|
||||
label.classList.add('label-active');
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
if (!input.value) {
|
||||
label.classList.remove('label-active');
|
||||
}
|
||||
});
|
||||
|
||||
// Check initial state
|
||||
if (input.value) {
|
||||
label.classList.add('label-active');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Form submission
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
|
||||
// Validate all inputs
|
||||
inputs.forEach(input => {
|
||||
if (!validateInput(input)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
// If form is valid, proceed with submission
|
||||
if (isValid) {
|
||||
submitForm(form);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Validate individual input
|
||||
function validateInput(input) {
|
||||
const validationMessage = input.parentNode.querySelector('.validation-message');
|
||||
let isValid = true;
|
||||
let message = '';
|
||||
|
||||
// Required field validation
|
||||
if (input.required && !input.value.trim()) {
|
||||
isValid = false;
|
||||
message = 'This field is required';
|
||||
}
|
||||
|
||||
// Email validation
|
||||
if (input.type === 'email' && input.value.trim()) {
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailPattern.test(input.value)) {
|
||||
isValid = false;
|
||||
message = 'Please enter a valid email address';
|
||||
}
|
||||
}
|
||||
|
||||
// Phone validation
|
||||
if (input.type === 'tel' && input.value.trim()) {
|
||||
const phonePattern = /^[\d\s\+\-\(\)]{7,20}$/;
|
||||
if (!phonePattern.test(input.value)) {
|
||||
isValid = false;
|
||||
message = 'Please enter a valid phone number';
|
||||
}
|
||||
}
|
||||
|
||||
// Custom validation based on data attributes
|
||||
if (input.dataset.minLength && input.value.length < parseInt(input.dataset.minLength)) {
|
||||
isValid = false;
|
||||
message = `Minimum length is ${input.dataset.minLength} characters`;
|
||||
}
|
||||
|
||||
// Update UI based on validation result
|
||||
if (!isValid) {
|
||||
input.classList.add('input-error');
|
||||
if (validationMessage) {
|
||||
validationMessage.textContent = message;
|
||||
validationMessage.classList.add('error-message');
|
||||
}
|
||||
} else {
|
||||
input.classList.remove('input-error');
|
||||
if (validationMessage) {
|
||||
validationMessage.textContent = '';
|
||||
validationMessage.classList.remove('error-message');
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Submit form with animation
|
||||
function submitForm(form) {
|
||||
// Create loading overlay
|
||||
const loadingOverlay = document.createElement('div');
|
||||
loadingOverlay.className = 'form-loading-overlay';
|
||||
loadingOverlay.innerHTML = `
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Submitting...</p>
|
||||
`;
|
||||
form.appendChild(loadingOverlay);
|
||||
|
||||
// Simulate form submission (replace with actual AJAX submission)
|
||||
setTimeout(() => {
|
||||
loadingOverlay.innerHTML = `
|
||||
<div class="success-icon">✓</div>
|
||||
<p>Thank you! We'll be in touch soon.</p>
|
||||
`;
|
||||
loadingOverlay.classList.add('submission-success');
|
||||
|
||||
// Reset form after success
|
||||
setTimeout(() => {
|
||||
form.reset();
|
||||
form.removeChild(loadingOverlay);
|
||||
|
||||
// Reset all floating labels
|
||||
const labels = form.querySelectorAll('.floating-label');
|
||||
labels.forEach(label => {
|
||||
label.classList.remove('label-active');
|
||||
});
|
||||
}, 3000);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Special handling for contact form
|
||||
function initContactForm(form) {
|
||||
// Add animation for form sections
|
||||
const formSections = form.querySelectorAll('.form-section');
|
||||
if (formSections.length > 1) {
|
||||
// Create navigation buttons
|
||||
const navigation = document.createElement('div');
|
||||
navigation.className = 'form-navigation';
|
||||
|
||||
let currentSection = 0;
|
||||
|
||||
// Initially hide all sections except the first
|
||||
formSections.forEach((section, index) => {
|
||||
if (index > 0) {
|
||||
section.classList.add('hidden-section');
|
||||
}
|
||||
});
|
||||
|
||||
// Next button functionality
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.type = 'button';
|
||||
nextBtn.className = 'btn btn-secondary next-section';
|
||||
nextBtn.textContent = 'Next';
|
||||
nextBtn.addEventListener('click', () => {
|
||||
// Validate current section before proceeding
|
||||
const currentInputs = formSections[currentSection].querySelectorAll('input, textarea, select');
|
||||
let sectionIsValid = true;
|
||||
|
||||
currentInputs.forEach(input => {
|
||||
if (!validateInput(input)) {
|
||||
sectionIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (sectionIsValid && currentSection < formSections.length - 1) {
|
||||
formSections[currentSection].classList.add('hidden-section');
|
||||
formSections[currentSection].classList.add('section-left');
|
||||
currentSection++;
|
||||
formSections[currentSection].classList.remove('hidden-section');
|
||||
|
||||
updateNavigationState();
|
||||
}
|
||||
});
|
||||
|
||||
// Previous button functionality
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.type = 'button';
|
||||
prevBtn.className = 'btn btn-outline prev-section';
|
||||
prevBtn.textContent = 'Previous';
|
||||
prevBtn.style.display = 'none';
|
||||
prevBtn.addEventListener('click', () => {
|
||||
if (currentSection > 0) {
|
||||
formSections[currentSection].classList.add('hidden-section');
|
||||
currentSection--;
|
||||
formSections[currentSection].classList.remove('hidden-section');
|
||||
formSections[currentSection].classList.remove('section-left');
|
||||
|
||||
updateNavigationState();
|
||||
}
|
||||
});
|
||||
|
||||
// Add navigation to form
|
||||
navigation.appendChild(prevBtn);
|
||||
navigation.appendChild(nextBtn);
|
||||
form.appendChild(navigation);
|
||||
|
||||
// Update navigation buttons state
|
||||
function updateNavigationState() {
|
||||
prevBtn.style.display = currentSection > 0 ? 'inline-block' : 'none';
|
||||
nextBtn.textContent = currentSection === formSections.length - 1 ? 'Submit' : 'Next';
|
||||
|
||||
// Replace next button with submit on last section
|
||||
if (currentSection === formSections.length - 1) {
|
||||
nextBtn.classList.add('submit-form');
|
||||
nextBtn.addEventListener('click', () => {
|
||||
form.dispatchEvent(new Event('submit'));
|
||||
});
|
||||
} else {
|
||||
nextBtn.classList.remove('submit-form');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Newsletter subscription form handling
|
||||
function initNewsletterForm(form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const emailInput = form.querySelector('input[type="email"]');
|
||||
if (!emailInput) return;
|
||||
|
||||
// Validate email
|
||||
if (!validateInput(emailInput)) return;
|
||||
|
||||
// Create animated check mark for subscription
|
||||
const subscribeBtn = form.querySelector('button[type="submit"]');
|
||||
if (subscribeBtn) {
|
||||
subscribeBtn.innerHTML = '<div class="subscribe-loader"></div>';
|
||||
subscribeBtn.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
subscribeBtn.innerHTML = '<span class="subscribe-check">✓</span>';
|
||||
subscribeBtn.classList.add('subscribed');
|
||||
|
||||
// Display thank you message
|
||||
const thankYou = document.createElement('div');
|
||||
thankYou.className = 'subscription-thanks';
|
||||
thankYou.textContent = 'Thank you for subscribing!';
|
||||
form.appendChild(thankYou);
|
||||
|
||||
// Reset form after delay
|
||||
setTimeout(() => {
|
||||
form.reset();
|
||||
subscribeBtn.innerHTML = 'Subscribe';
|
||||
subscribeBtn.disabled = false;
|
||||
subscribeBtn.classList.remove('subscribed');
|
||||
|
||||
if (form.contains(thankYou)) {
|
||||
form.removeChild(thankYou);
|
||||
}
|
||||
}, 3000);
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
//
|
||||
273
js/hero-animation-new.js
Normal file
273
js/hero-animation-new.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* Hero Animation - Neural network visualization and interactive node effects
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize neural mesh canvas
|
||||
initNeuralMesh();
|
||||
|
||||
// Initialize data nodes interaction
|
||||
initDataNodes();
|
||||
|
||||
// Initialize morphing shapes animation
|
||||
initMorphingShapes();
|
||||
|
||||
// Initialize data streams animation
|
||||
initDataStreams();
|
||||
});
|
||||
|
||||
// Create neural mesh network visualization
|
||||
function initNeuralMesh() {
|
||||
const canvas = document.querySelector('.mesh-canvas');
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
// Neural network settings
|
||||
const nodeCount = 120;
|
||||
const nodes = [];
|
||||
const connections = [];
|
||||
const connectionDistance = 150;
|
||||
const nodeSize = 2;
|
||||
|
||||
// Create nodes
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
nodes.push({
|
||||
x: Math.random() * width,
|
||||
y: Math.random() * height,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
size: Math.random() * nodeSize + 1,
|
||||
color: `rgba(255, 255, 255, ${Math.random() * 0.5 + 0.1})`
|
||||
});
|
||||
}
|
||||
|
||||
// Animation loop
|
||||
function animate() {
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Draw connections
|
||||
connections.length = 0;
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const nodeA = nodes[i];
|
||||
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const nodeB = nodes[j];
|
||||
|
||||
const dx = nodeB.x - nodeA.x;
|
||||
const dy = nodeB.y - nodeA.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < connectionDistance) {
|
||||
const opacity = 1 - (distance / connectionDistance);
|
||||
connections.push({
|
||||
x1: nodeA.x,
|
||||
y1: nodeA.y,
|
||||
x2: nodeB.x,
|
||||
y2: nodeB.y,
|
||||
opacity
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw connections
|
||||
for (const connection of connections) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(connection.x1, connection.y1);
|
||||
ctx.lineTo(connection.x2, connection.y2);
|
||||
ctx.strokeStyle = `rgba(80, 200, 255, ${connection.opacity * 0.3})`;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw and update nodes
|
||||
for (const node of nodes) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, node.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = node.color;
|
||||
ctx.fill();
|
||||
|
||||
// Update position
|
||||
node.x += node.vx;
|
||||
node.y += node.vy;
|
||||
|
||||
// Bounce off edges
|
||||
if (node.x < 0 || node.x > width) node.vx *= -1;
|
||||
if (node.y < 0 || node.y > height) node.vy *= -1;
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// Start animation
|
||||
animate();
|
||||
|
||||
// Add mouse interaction
|
||||
canvas.addEventListener('mousemove', function(e) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
// Add attraction to mouse position for nearby nodes
|
||||
for (const node of nodes) {
|
||||
const dx = mouseX - node.x;
|
||||
const dy = mouseY - node.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
node.x += dx * 0.02;
|
||||
node.y += dy * 0.02;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize interactive data nodes
|
||||
function initDataNodes() {
|
||||
const nodes = document.querySelectorAll('.data-node');
|
||||
|
||||
nodes.forEach(node => { // Add hover effects
|
||||
node.addEventListener('mouseenter', function() {
|
||||
// Highlight node
|
||||
this.classList.add('node-hover');
|
||||
|
||||
// Create connecting lines to related nodes
|
||||
const tech = this.getAttribute('data-tech');
|
||||
const connectedTech = getRelatedTechnologies(tech);
|
||||
nodes.forEach(otherNode => {
|
||||
const otherTech = otherNode.getAttribute('data-tech');
|
||||
if (otherTech && connectedTech.includes(otherTech)) {
|
||||
otherNode.classList.add('node-connected');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove effects on mouse leave
|
||||
node.addEventListener('mouseleave', function() {
|
||||
// Remove highlight
|
||||
this.classList.remove('node-hover');
|
||||
|
||||
// Remove connecting lines
|
||||
nodes.forEach(otherNode => {
|
||||
otherNode.classList.remove('node-connected');
|
||||
});
|
||||
});
|
||||
|
||||
// Add click interaction
|
||||
node.addEventListener('click', function() {
|
||||
const tech = this.getAttribute('data-tech');
|
||||
if (!tech) return;
|
||||
|
||||
// Get corresponding URL for the technology
|
||||
const url = getTechnologyPageUrl(tech);
|
||||
if (url) {
|
||||
// Create click pulse effect
|
||||
const pulse = document.createElement('div');
|
||||
pulse.className = 'node-pulse';
|
||||
this.appendChild(pulse);
|
||||
|
||||
// Navigate after animation
|
||||
setTimeout(() => {
|
||||
window.location.href = url;
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add pulsing animation to core nodes
|
||||
const coreNodes = document.querySelectorAll('.node-core');
|
||||
coreNodes.forEach(node => {
|
||||
setInterval(() => {
|
||||
node.classList.add('core-pulse-active');
|
||||
setTimeout(() => {
|
||||
node.classList.remove('core-pulse-active');
|
||||
}, 1500);
|
||||
}, 3000 + Math.random() * 2000); // Randomize timing for natural effect
|
||||
});
|
||||
}
|
||||
|
||||
// Map technology names to related technologies
|
||||
function getRelatedTechnologies(tech) {
|
||||
const techMap = {
|
||||
'Machine Learning': ['Data Analytics', 'Generative AI', 'Agentic AI'],
|
||||
'Generative AI': ['Machine Learning', 'Data Analytics', 'Media & Entertainment'],
|
||||
'Data Analytics': ['Machine Learning', 'Finance', 'Retail'],
|
||||
'Automation': ['Agentic AI', 'Manufacturing', 'Healthcare'],
|
||||
'Finance': ['Data Analytics', 'Machine Learning'],
|
||||
'Healthcare': ['Machine Learning', 'Automation'],
|
||||
'Retail': ['Data Analytics', 'Machine Learning'],
|
||||
'Manufacturing': ['Automation', 'Data Analytics'],
|
||||
'Agentic AI': ['Machine Learning', 'Automation'],
|
||||
'Marketing': ['Data Analytics', 'Generative AI'],
|
||||
'Media & Entertainment': ['Generative AI', 'Data Analytics'],
|
||||
'Travel': ['Data Analytics', 'Automation'],
|
||||
'Education': ['Generative AI', 'Machine Learning'],
|
||||
'Technology': ['Machine Learning', 'Agentic AI', 'Data Analytics', 'Automation']
|
||||
};
|
||||
|
||||
return techMap[tech] || [];
|
||||
}
|
||||
|
||||
// Map technology names to page URLs
|
||||
function getTechnologyPageUrl(tech) {
|
||||
const urlMap = {
|
||||
'Machine Learning': 'Services/machine-learning.html',
|
||||
'Generative AI': 'Services/generative-ai.html',
|
||||
'Data Analytics': 'Services/data-analysis.html',
|
||||
'Automation': 'Services/automation-flows.html',
|
||||
'Agentic AI': 'Services/agentic-ai.html',
|
||||
'Finance': 'Industries/finance.html',
|
||||
'Healthcare': 'Industries/healthcare.html',
|
||||
'Retail': 'Industries/retail.html',
|
||||
'Manufacturing': 'Industries/manufacturing.html',
|
||||
'Marketing': 'Industries/marketing.html',
|
||||
'Media & Entertainment': 'Industries/media-entertainment.html',
|
||||
'Travel': 'Industries/travel.html',
|
||||
'Education': 'Education/workshops.html',
|
||||
'Technology': 'about.html'
|
||||
};
|
||||
|
||||
return urlMap[tech] || null;
|
||||
}
|
||||
|
||||
// Initialize morphing geometric shapes
|
||||
function initMorphingShapes() {
|
||||
const morphShapes = document.querySelectorAll('.morph-shape');
|
||||
|
||||
morphShapes.forEach(shape => {
|
||||
// Random initial properties
|
||||
const size = Math.random() * 100 + 50;
|
||||
const opacity = Math.random() * 0.15 + 0.05;
|
||||
const rotation = Math.random() * 360;
|
||||
const duration = Math.random() * 10 + 15;
|
||||
const delay = Math.random() * 5;
|
||||
|
||||
// Set initial style
|
||||
shape.style.width = `${size}px`;
|
||||
shape.style.height = `${size}px`;
|
||||
shape.style.opacity = opacity;
|
||||
shape.style.transform = `rotate(${rotation}deg)`;
|
||||
shape.style.animationDuration = `${duration}s`;
|
||||
shape.style.animationDelay = `${delay}s`;
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize data streams animation
|
||||
function initDataStreams() {
|
||||
const streams = document.querySelectorAll('.stream, .stream-horizontal, .stream-diagonal');
|
||||
|
||||
streams.forEach(stream => {
|
||||
// Random animation properties
|
||||
const duration = Math.random() * 8 + 4;
|
||||
const delay = Math.random() * 3;
|
||||
|
||||
// Apply animation with random timing
|
||||
stream.style.animationDuration = `${duration}s`;
|
||||
stream.style.animationDelay = `${delay}s`;
|
||||
});
|
||||
}
|
||||
137
js/main.js
137
js/main.js
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Main.js - Core interaction and animation functionality for Gyannetra website
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all interactive components
|
||||
initScrollAnimations();
|
||||
initParallaxEffects();
|
||||
initSectionTransitions();
|
||||
initCursorEffects();
|
||||
|
||||
// Remove page loader when everything is ready
|
||||
setTimeout(() => {
|
||||
const body = document.querySelector('body');
|
||||
if (body) body.classList.add('loaded');
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Animate elements when they enter viewport
|
||||
function initScrollAnimations() {
|
||||
const animatedElements = document.querySelectorAll('.exp-item, .section-heading, .hero-content, .btn-primary');
|
||||
|
||||
// Create intersection observer
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('animated');
|
||||
// Stop observing after animation is triggered
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.2,
|
||||
rootMargin: '0px 0px -10% 0px'
|
||||
});
|
||||
|
||||
// Observe all animated elements
|
||||
animatedElements.forEach(element => {
|
||||
observer.observe(element);
|
||||
// Add base animation class
|
||||
element.classList.add('animate-on-scroll');
|
||||
});
|
||||
}
|
||||
|
||||
// Parallax background effects
|
||||
function initParallaxEffects() {
|
||||
const parallaxElements = document.querySelectorAll('.atmospheric-bg, .light-effect-top, .light-effect-bottom');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
parallaxElements.forEach((element, index) => {
|
||||
// Different parallax speeds based on element
|
||||
const speed = index * 0.1 + 0.1;
|
||||
element.style.transform = `translateY(${scrollY * speed}px)`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth section transitions
|
||||
function initSectionTransitions() {
|
||||
const sections = document.querySelectorAll('section');
|
||||
|
||||
sections.forEach(section => {
|
||||
section.classList.add('transition-ready');
|
||||
});
|
||||
}
|
||||
|
||||
// Custom cursor effects for interactive elements
|
||||
function initCursorEffects() {
|
||||
const interactiveElements = document.querySelectorAll('a, button, .data-node, .tech-marquee-item');
|
||||
const body = document.body;
|
||||
|
||||
// Create custom cursor elements
|
||||
const cursor = document.createElement('div');
|
||||
cursor.className = 'custom-cursor';
|
||||
body.appendChild(cursor);
|
||||
|
||||
const cursorDot = document.createElement('div');
|
||||
cursorDot.className = 'cursor-dot';
|
||||
body.appendChild(cursorDot);
|
||||
|
||||
// Update cursor position
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
cursor.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
|
||||
cursorDot.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
|
||||
});
|
||||
|
||||
// Hover effects
|
||||
interactiveElements.forEach(element => {
|
||||
element.addEventListener('mouseenter', () => {
|
||||
cursor.classList.add('cursor-hover');
|
||||
cursorDot.classList.add('cursor-hover');
|
||||
});
|
||||
|
||||
element.addEventListener('mouseleave', () => {
|
||||
cursor.classList.remove('cursor-hover');
|
||||
cursorDot.classList.remove('cursor-hover');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Watch for page visibility to optimize performance
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.hidden) {
|
||||
document.body.classList.add('page-hidden');
|
||||
} else {
|
||||
document.body.classList.remove('page-hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Add smooth scrolling to anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const targetId = this.getAttribute('href');
|
||||
if (targetId === '#') return;
|
||||
|
||||
const targetElement = document.querySelector(targetId);
|
||||
if (targetElement) {
|
||||
// Add animation class
|
||||
targetElement.classList.add('section-highlighted');
|
||||
|
||||
// Smooth scroll
|
||||
targetElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
|
||||
// Remove class after animation
|
||||
setTimeout(() => {
|
||||
targetElement.classList.remove('section-highlighted');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// Fallback: Enhance dropdowns with fade/slide effect if not handled elsewhere
|
||||
// (dropdown.js already handles logic, so this is for extra UI polish)
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.dropdown-menu').forEach(menu => {
|
||||
menu.addEventListener('transitionend', function() {
|
||||
if (!menu.classList.contains('show')) {
|
||||
menu.style.display = '';
|
||||
}
|
||||
});
|
||||
menu.addEventListener('show', function() {
|
||||
menu.style.display = 'block';
|
||||
});
|
||||
});
|
||||
});
|
||||
289
js/page-specific.js
Normal file
289
js/page-specific.js
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* Page-specific animations and effects for Gyannetra Website
|
||||
* Handles unique interactions for different sections and pages
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Detect current page
|
||||
const currentPath = window.location.pathname;
|
||||
const pageName = currentPath.split('/').pop() || 'index.html';
|
||||
|
||||
// Initialize common elements
|
||||
initCommonElements();
|
||||
|
||||
// Initialize page-specific effects
|
||||
switch (pageName) {
|
||||
case 'index.html':
|
||||
initHomePage();
|
||||
break;
|
||||
case 'about.html':
|
||||
initAboutPage();
|
||||
break;
|
||||
case 'contact.html':
|
||||
initContactPage();
|
||||
break;
|
||||
default:
|
||||
// Check if it's a service page
|
||||
if (currentPath.includes('/Services/')) {
|
||||
initServicePage();
|
||||
}
|
||||
// Check if it's an industry page
|
||||
else if (currentPath.includes('/Industries/')) {
|
||||
initIndustryPage();
|
||||
}
|
||||
// Check if it's an education page
|
||||
else if (currentPath.includes('/Education/')) {
|
||||
initEducationPage();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize elements common to all pages
|
||||
function initCommonElements() {
|
||||
// Initialize header scroll effect
|
||||
initHeaderScroll();
|
||||
|
||||
// Initialize footer animations
|
||||
initFooterAnimations();
|
||||
|
||||
// Initialize section entrance animations
|
||||
initSectionAnimations();
|
||||
}
|
||||
|
||||
// Header animation on scroll
|
||||
function initHeaderScroll() {
|
||||
const header = document.querySelector('.site-header');
|
||||
if (!header) return;
|
||||
|
||||
let lastScrollY = 0;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
// Add shadow and background when scrolling down
|
||||
if (scrollY > 20) {
|
||||
header.classList.add('header-scrolled');
|
||||
} else {
|
||||
header.classList.remove('header-scrolled');
|
||||
}
|
||||
|
||||
// Hide header when scrolling down quickly, show when scrolling up
|
||||
if (scrollY > lastScrollY + 50) {
|
||||
header.classList.add('header-hidden');
|
||||
} else if (scrollY < lastScrollY - 10) {
|
||||
header.classList.remove('header-hidden');
|
||||
}
|
||||
|
||||
lastScrollY = scrollY;
|
||||
});
|
||||
}
|
||||
|
||||
// Homepage specific animations
|
||||
function initHomePage() {
|
||||
// Initialize expertise item animations
|
||||
initExpertiseItems();
|
||||
|
||||
// Initialize tech section parallax
|
||||
initTechParallax();
|
||||
}
|
||||
|
||||
// Expertise items hover and click effects
|
||||
function initExpertiseItems() {
|
||||
const expItems = document.querySelectorAll('.exp-item');
|
||||
|
||||
expItems.forEach(item => {
|
||||
// Add hover effect
|
||||
item.addEventListener('mouseenter', function() {
|
||||
this.classList.add('exp-hover');
|
||||
});
|
||||
|
||||
item.addEventListener('mouseleave', function() {
|
||||
this.classList.remove('exp-hover');
|
||||
});
|
||||
|
||||
// Add click animation
|
||||
item.addEventListener('click', function() {
|
||||
this.classList.add('exp-click');
|
||||
|
||||
setTimeout(() => {
|
||||
this.classList.remove('exp-click');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
|
||||
// Add staggered entrance animation
|
||||
expItems.forEach((item, index) => {
|
||||
// Set staggered animation delay
|
||||
item.style.animationDelay = `${index * 0.15}s`;
|
||||
|
||||
// Add animation class
|
||||
item.classList.add('animate-entrance');
|
||||
});
|
||||
}
|
||||
|
||||
// Tech section parallax effect
|
||||
function initTechParallax() {
|
||||
const techSection = document.getElementById('technologies');
|
||||
if (!techSection) return;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
// Get section position
|
||||
const rect = techSection.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
// Check if section is in viewport
|
||||
if (rect.top < viewportHeight && rect.bottom > 0) {
|
||||
// Calculate percentage scrolled through the section
|
||||
const scrollPercentage = 1 - Math.max(0, Math.min(1, rect.bottom / (rect.height + viewportHeight)));
|
||||
|
||||
// Apply parallax effect to tech icons
|
||||
const techItems = document.querySelectorAll('.tech-icon-container');
|
||||
techItems.forEach((item, index) => {
|
||||
const offsetFactor = (index % 3 - 1) * 20; // Different offset for each item
|
||||
const translateY = scrollPercentage * offsetFactor;
|
||||
item.style.transform = `translateY(${translateY}px)`;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// About page specific animations
|
||||
function initAboutPage() {
|
||||
// Team member hover effects
|
||||
const teamMembers = document.querySelectorAll('.team-member');
|
||||
if (teamMembers.length > 0) {
|
||||
teamMembers.forEach(member => {
|
||||
member.addEventListener('mouseenter', function() {
|
||||
this.classList.add('member-hover');
|
||||
});
|
||||
|
||||
member.addEventListener('mouseleave', function() {
|
||||
this.classList.remove('member-hover');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Contact page specific animations
|
||||
function initContactPage() {
|
||||
// Form input effects
|
||||
const formInputs = document.querySelectorAll('input, textarea');
|
||||
if (formInputs.length > 0) {
|
||||
formInputs.forEach(input => {
|
||||
// Add focus effect
|
||||
input.addEventListener('focus', function() {
|
||||
this.parentElement.classList.add('input-focused');
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
if (!this.value) {
|
||||
this.parentElement.classList.remove('input-focused');
|
||||
}
|
||||
});
|
||||
|
||||
// Check for pre-filled inputs
|
||||
if (input.value) {
|
||||
input.parentElement.classList.add('input-focused');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Service page specific animations
|
||||
function initServicePage() {
|
||||
// Service cards animation
|
||||
const serviceCards = document.querySelectorAll('.service-card');
|
||||
if (serviceCards.length > 0) {
|
||||
serviceCards.forEach((card, index) => {
|
||||
// Add staggered animation
|
||||
card.style.animationDelay = `${index * 0.15}s`;
|
||||
card.classList.add('animate-slide-up');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Industry page specific animations
|
||||
function initIndustryPage() {
|
||||
// Case study hover effects
|
||||
const caseStudies = document.querySelectorAll('.case-study');
|
||||
if (caseStudies.length > 0) {
|
||||
caseStudies.forEach(study => {
|
||||
study.addEventListener('mouseenter', function() {
|
||||
this.classList.add('case-hover');
|
||||
});
|
||||
|
||||
study.addEventListener('mouseleave', function() {
|
||||
this.classList.remove('case-hover');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Education page specific animations
|
||||
function initEducationPage() {
|
||||
// Course card effects
|
||||
const courseCards = document.querySelectorAll('.course-card');
|
||||
if (courseCards.length > 0) {
|
||||
courseCards.forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
this.classList.toggle('card-expanded');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Section entrance animations
|
||||
function initSectionAnimations() {
|
||||
const sections = document.querySelectorAll('section');
|
||||
|
||||
// Create intersection observer
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('section-visible');
|
||||
|
||||
// Animate children with delay
|
||||
const children = entry.target.querySelectorAll('.animate-on-visible');
|
||||
children.forEach((child, index) => {
|
||||
child.style.animationDelay = `${index * 0.1}s`;
|
||||
child.classList.add('element-visible');
|
||||
});
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.15,
|
||||
rootMargin: '0px 0px -10% 0px'
|
||||
});
|
||||
|
||||
// Observe all sections
|
||||
sections.forEach(section => {
|
||||
observer.observe(section);
|
||||
section.classList.add('section-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Footer animations
|
||||
function initFooterAnimations() {
|
||||
const footer = document.querySelector('.site-footer');
|
||||
if (!footer) return;
|
||||
|
||||
// Create intersection observer for footer
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
footer.classList.add('footer-visible');
|
||||
|
||||
// Staggered animation for footer columns
|
||||
const columns = footer.querySelectorAll('.footer-col');
|
||||
columns.forEach((col, index) => {
|
||||
col.style.animationDelay = `${index * 0.15}s`;
|
||||
col.classList.add('col-visible');
|
||||
});
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.2
|
||||
});
|
||||
|
||||
observer.observe(footer);
|
||||
}
|
||||
@@ -39,37 +39,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Function to add pulse effect to random icons in a marquee
|
||||
function addPulseEffects() {
|
||||
// Get all tech marquees
|
||||
const marquees = document.querySelectorAll('.tech-marquee-container');
|
||||
|
||||
marquees.forEach(marquee => {
|
||||
// Get all items in this marquee
|
||||
const marqueeItems = marquee.querySelectorAll('.tech-marquee-item');
|
||||
|
||||
// Select 1-2 random items to pulse
|
||||
const numberOfPulses = Math.floor(Math.random() * 2) + 1;
|
||||
|
||||
for (let i = 0; i < numberOfPulses; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * marqueeItems.length);
|
||||
const randomItem = marqueeItems[randomIndex];
|
||||
const iconContainer = randomItem.querySelector('.tech-icon-container');
|
||||
|
||||
if (iconContainer && !iconContainer.classList.contains('tech-pulse-highlight')) {
|
||||
iconContainer.classList.add('tech-pulse-highlight');
|
||||
|
||||
setTimeout(() => {
|
||||
iconContainer.classList.remove('tech-pulse-highlight');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add subtle pulse effects periodically
|
||||
setInterval(addPulseEffects, 3000);
|
||||
// Removed pulse effects functionality - displaying raw icons without glow effects
|
||||
// Add a parallax effect to create depth between the two marquees
|
||||
window.addEventListener('mousemove', function(e) {
|
||||
const containers = document.querySelectorAll('.tech-marquee-container');
|
||||
|
||||
813
js/unified-neural-interactions.js
Normal file
813
js/unified-neural-interactions.js
Normal file
@@ -0,0 +1,813 @@
|
||||
/**
|
||||
* Enhanced Unified Neural Network Animation and Data Node Interactive Effects
|
||||
* This file provides advanced visual effects and interactive animations for the neural network and data nodes
|
||||
* with optimized performance and responsive behavior across devices
|
||||
*/
|
||||
|
||||
// Neural Network Canvas Animation Class
|
||||
class NeuralNetworkAnimation {
|
||||
constructor() {
|
||||
this.canvas = document.querySelector('.mesh-canvas');
|
||||
this.ctx = this.canvas ? this.canvas.getContext('2d') : null;
|
||||
this.nodes = [];
|
||||
this.connections = [];
|
||||
this.mousePos = { x: 0, y: 0 };
|
||||
this.animationId = null;
|
||||
|
||||
if (this.ctx) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupCanvas();
|
||||
this.createNodes();
|
||||
this.setupInteractions();
|
||||
this.setupResize();
|
||||
this.animate();
|
||||
}
|
||||
|
||||
setupCanvas() {
|
||||
const heroSection = document.getElementById('hero');
|
||||
if (heroSection) {
|
||||
const rect = heroSection.getBoundingClientRect();
|
||||
this.canvas.width = rect.width;
|
||||
this.canvas.height = rect.height;
|
||||
|
||||
this.canvas.style.width = '100%';
|
||||
this.canvas.style.height = '100%';
|
||||
}
|
||||
}
|
||||
|
||||
setupResize() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.setupCanvas();
|
||||
this.nodes.forEach(node => {
|
||||
node.x = Math.min(node.x, this.canvas.width);
|
||||
node.y = Math.min(node.y, this.canvas.height);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createNodes() {
|
||||
const nodeCount = 40;
|
||||
this.nodes = [];
|
||||
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
this.nodes.push({
|
||||
x: Math.random() * this.canvas.width,
|
||||
y: Math.random() * this.canvas.height,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
radius: Math.random() * 3 + 1,
|
||||
opacity: Math.random() * 0.5 + 0.2,
|
||||
pulsePhase: Math.random() * Math.PI * 2,
|
||||
energy: Math.random()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateNodes() {
|
||||
this.nodes.forEach(node => {
|
||||
// Update position
|
||||
node.x += node.vx;
|
||||
node.y += node.vy;
|
||||
|
||||
// Bounce off edges
|
||||
if (node.x <= 0 || node.x >= this.canvas.width) node.vx *= -1;
|
||||
if (node.y <= 0 || node.y >= this.canvas.height) node.vy *= -1;
|
||||
|
||||
// Keep nodes in bounds
|
||||
node.x = Math.max(0, Math.min(this.canvas.width, node.x));
|
||||
node.y = Math.max(0, Math.min(this.canvas.height, node.y));
|
||||
|
||||
// Update pulse
|
||||
node.pulsePhase += 0.02;
|
||||
node.energy = 0.5 + 0.5 * Math.sin(node.pulsePhase);
|
||||
});
|
||||
}
|
||||
|
||||
drawNodes() {
|
||||
this.nodes.forEach(node => {
|
||||
const gradient = this.ctx.createRadialGradient(
|
||||
node.x, node.y, 0,
|
||||
node.x, node.y, node.radius * 3
|
||||
);
|
||||
|
||||
const alpha = node.opacity * node.energy;
|
||||
gradient.addColorStop(0, `rgba(59, 130, 246, ${alpha})`);
|
||||
gradient.addColorStop(0.5, `rgba(139, 92, 246, ${alpha * 0.7})`);
|
||||
gradient.addColorStop(1, `rgba(236, 72, 153, 0)`);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
|
||||
// Core dot
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(node.x, node.y, node.radius * 0.3, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.8})`;
|
||||
this.ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
drawConnections() {
|
||||
const maxDistance = 120;
|
||||
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
for (let j = i + 1; j < this.nodes.length; j++) {
|
||||
const nodeA = this.nodes[i];
|
||||
const nodeB = this.nodes[j];
|
||||
|
||||
const dx = nodeB.x - nodeA.x;
|
||||
const dy = nodeB.y - nodeA.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < maxDistance) {
|
||||
const opacity = (1 - distance / maxDistance) * 0.3;
|
||||
const energyBoost = (nodeA.energy + nodeB.energy) / 2;
|
||||
|
||||
// Create dynamic gradient line
|
||||
const gradient = this.ctx.createLinearGradient(
|
||||
nodeA.x, nodeA.y, nodeB.x, nodeB.y
|
||||
);
|
||||
|
||||
gradient.addColorStop(0, `rgba(59, 130, 246, ${opacity * energyBoost})`);
|
||||
gradient.addColorStop(0.5, `rgba(139, 92, 246, ${opacity * energyBoost * 0.8})`);
|
||||
gradient.addColorStop(1, `rgba(236, 72, 153, ${opacity * energyBoost * 0.6})`);
|
||||
|
||||
this.ctx.strokeStyle = gradient;
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.globalCompositeOperation = 'lighter';
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(nodeA.x, nodeA.y);
|
||||
this.ctx.lineTo(nodeB.x, nodeB.y);
|
||||
this.ctx.stroke();
|
||||
|
||||
this.ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// Data pulse along connection
|
||||
if (Math.random() < 0.01) {
|
||||
this.createDataPulse(nodeA, nodeB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createDataPulse(nodeA, nodeB) {
|
||||
const pulse = {
|
||||
startX: nodeA.x,
|
||||
startY: nodeA.y,
|
||||
endX: nodeB.x,
|
||||
endY: nodeB.y,
|
||||
progress: 0,
|
||||
speed: 0.02,
|
||||
life: 1
|
||||
};
|
||||
|
||||
this.dataPulses = this.dataPulses || [];
|
||||
this.dataPulses.push(pulse);
|
||||
}
|
||||
|
||||
updateDataPulses() {
|
||||
if (!this.dataPulses) return;
|
||||
|
||||
this.dataPulses = this.dataPulses.filter(pulse => {
|
||||
pulse.progress += pulse.speed;
|
||||
pulse.life -= 0.01;
|
||||
|
||||
if (pulse.progress >= 1 || pulse.life <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Draw pulse
|
||||
const x = pulse.startX + (pulse.endX - pulse.startX) * pulse.progress;
|
||||
const y = pulse.startY + (pulse.endY - pulse.startY) * pulse.progress;
|
||||
|
||||
const gradient = this.ctx.createRadialGradient(x, y, 0, x, y, 6);
|
||||
gradient.addColorStop(0, `rgba(255, 255, 255, ${pulse.life})`);
|
||||
gradient.addColorStop(1, `rgba(59, 130, 246, 0)`);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(x, y, 4, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseMove(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
this.mousePos.x = e.clientX - rect.left;
|
||||
this.mousePos.y = e.clientY - rect.top;
|
||||
|
||||
// Create attraction effect
|
||||
this.nodes.forEach(node => {
|
||||
const dx = this.mousePos.x - node.x;
|
||||
const dy = this.mousePos.y - node.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
const force = (100 - distance) / 100 * 0.01;
|
||||
node.vx += dx * force;
|
||||
node.vy += dy * force;
|
||||
node.energy = Math.min(1, node.energy + 0.1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animate() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Add subtle background gradient
|
||||
const bgGradient = this.ctx.createRadialGradient(
|
||||
this.canvas.width / 2, this.canvas.height / 2, 0,
|
||||
this.canvas.width / 2, this.canvas.height / 2, Math.max(this.canvas.width, this.canvas.height) / 2
|
||||
);
|
||||
bgGradient.addColorStop(0, 'rgba(59, 130, 246, 0.02)');
|
||||
bgGradient.addColorStop(1, 'rgba(139, 92, 246, 0.01)');
|
||||
|
||||
this.ctx.fillStyle = bgGradient;
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.updateNodes();
|
||||
this.drawConnections();
|
||||
this.drawNodes();
|
||||
this.updateDataPulses();
|
||||
|
||||
this.animationId = requestAnimationFrame(() => this.animate());
|
||||
}
|
||||
|
||||
setupInteractions() {
|
||||
// Mouse interaction only - data node interactions handled by DataNodeInteractions class
|
||||
this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data Node Interactive Effects Class
|
||||
class DataNodeInteractions {
|
||||
constructor() {
|
||||
this.tooltips = new Map();
|
||||
this.initializeInteractions();
|
||||
this.addStyles();
|
||||
} initializeInteractions() {
|
||||
console.log('Initializing data node interactions...');
|
||||
|
||||
// Look for data nodes in both containers
|
||||
const neuralNetworkNodes = document.querySelectorAll('.neural-network-container .data-node');
|
||||
const heroAnimationNodes = document.querySelectorAll('.hero-animation .data-node');
|
||||
|
||||
console.log(`Found ${neuralNetworkNodes.length} data nodes in neural-network-container`);
|
||||
console.log(`Found ${heroAnimationNodes.length} data nodes in hero-animation`);
|
||||
|
||||
// Debug: Check computed styles and verify they're interactive
|
||||
console.log('=== NEURAL NETWORK NODES ===');
|
||||
neuralNetworkNodes.forEach((node, index) => {
|
||||
const label = node.querySelector('.node-label')?.textContent?.trim();
|
||||
const classes = node.className;
|
||||
const computedStyle = window.getComputedStyle(node);
|
||||
const zIndex = computedStyle.zIndex;
|
||||
const pointerEvents = computedStyle.pointerEvents;
|
||||
|
||||
console.log(`Neural Node ${index + 1}: "${label}" (z-index: ${zIndex}, pointer-events: ${pointerEvents})`);
|
||||
|
||||
// Add visual debug indicator for 5 seconds
|
||||
node.style.border = '3px solid red';
|
||||
node.style.boxShadow = '0 0 10px red';
|
||||
setTimeout(() => {
|
||||
node.style.border = '';
|
||||
node.style.boxShadow = '';
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
console.log('=== HERO ANIMATION NODES ===');
|
||||
heroAnimationNodes.forEach((node, index) => {
|
||||
const label = node.querySelector('.node-label')?.textContent?.trim();
|
||||
const classes = node.className;
|
||||
const computedStyle = window.getComputedStyle(node);
|
||||
const zIndex = computedStyle.zIndex;
|
||||
const pointerEvents = computedStyle.pointerEvents;
|
||||
|
||||
console.log(`Hero Node ${index + 1}: "${label}" (z-index: ${zIndex}, pointer-events: ${pointerEvents})`);
|
||||
|
||||
// Add visual debug indicator for 5 seconds
|
||||
node.style.border = '3px solid blue';
|
||||
node.style.boxShadow = '0 0 10px blue';
|
||||
setTimeout(() => {
|
||||
node.style.border = '';
|
||||
node.style.boxShadow = '';
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
// Combine all data nodes
|
||||
const allDataNodes = [...neuralNetworkNodes, ...heroAnimationNodes];
|
||||
console.log(`Total data nodes found: ${allDataNodes.length}`);
|
||||
|
||||
// Verify no overlapping elements are blocking the neural network nodes
|
||||
if (neuralNetworkNodes.length > 0) {
|
||||
console.log('=== CHECKING FOR BLOCKING ELEMENTS ===');
|
||||
neuralNetworkNodes.forEach((node, index) => {
|
||||
const rect = node.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const elementAtPoint = document.elementFromPoint(centerX, centerY);
|
||||
|
||||
if (elementAtPoint !== node && !node.contains(elementAtPoint)) {
|
||||
console.warn(`Node ${index + 1} may be blocked by:`, elementAtPoint);
|
||||
} else {
|
||||
console.log(`Node ${index + 1} is accessible at coordinates (${centerX}, ${centerY})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
allDataNodes.forEach((node, index) => {
|
||||
const nodeTextElement = node.querySelector('.node-text') || node.querySelector('.node-label');
|
||||
const nodeText = nodeTextElement?.textContent?.trim();
|
||||
console.log(`Setting up interactions for node ${index + 1}: "${nodeText}"`);
|
||||
|
||||
// Create and setup tooltip
|
||||
this.setupTooltip(node);
|
||||
|
||||
// Add hover effects
|
||||
this.setupHoverEffects(node);
|
||||
|
||||
// Add click effects
|
||||
this.setupClickEffects(node);
|
||||
});
|
||||
|
||||
console.log('Data node interactions initialization complete');
|
||||
}
|
||||
setupTooltip(node) {
|
||||
// Check for both .node-text and .node-label structures
|
||||
const nodeTextElement = node.querySelector('.node-text') || node.querySelector('.node-label');
|
||||
const nodeText = nodeTextElement?.textContent?.trim();
|
||||
if (!nodeText) return;
|
||||
|
||||
// Create tooltip element
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'data-node-tooltip';
|
||||
tooltip.innerHTML = `
|
||||
<div class="tooltip-header">${nodeText}</div>
|
||||
<div class="tooltip-content">${this.getTooltipContent(nodeText)}</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(tooltip);
|
||||
this.tooltips.set(node, tooltip);
|
||||
|
||||
// Show tooltip on hover
|
||||
node.addEventListener('mouseenter', (e) => {
|
||||
this.showTooltip(node, e);
|
||||
});
|
||||
|
||||
// Update tooltip position on mouse move
|
||||
node.addEventListener('mousemove', (e) => {
|
||||
this.updateTooltipPosition(node, e);
|
||||
});
|
||||
|
||||
// Hide tooltip on leave
|
||||
node.addEventListener('mouseleave', () => {
|
||||
this.hideTooltip(node);
|
||||
});
|
||||
} getTooltipContent(nodeText) {
|
||||
const tooltipMap = {
|
||||
'ML': 'Machine Learning algorithms for pattern recognition, predictive analytics, and automated decision-making across industries.',
|
||||
'Gen AI': 'Generative AI for content creation, code generation, and innovative solutions using advanced language models.',
|
||||
'Analytics': 'Advanced analytics and business intelligence for data-driven insights and strategic decision making.',
|
||||
'Automation': 'Intelligent process automation to streamline workflows, reduce costs, and improve operational efficiency.',
|
||||
'Finance': 'AI-powered financial services including algorithmic trading, risk assessment, and fraud detection.',
|
||||
'Healthcare': 'Medical AI for diagnosis, treatment optimization, drug discovery, and personalized patient care.',
|
||||
'Retail': 'E-commerce optimization, inventory management, and personalized customer experiences.',
|
||||
'Manufacturing': 'Smart manufacturing with predictive maintenance, quality control, and supply chain optimization.',
|
||||
'Agentic AI': 'Advanced autonomous AI agents that can perform complex tasks and make decisions independently.',
|
||||
'Marketing': 'AI-powered marketing automation, customer segmentation, and campaign optimization.',
|
||||
'Media': 'Content creation, recommendation systems, and audience engagement optimization.',
|
||||
'Travel': 'Smart travel planning, booking optimization, and personalized travel experiences.',
|
||||
'Education': 'Personalized learning platforms, educational content generation, and student performance analytics.',
|
||||
'Technology': 'Cutting-edge AI technology solutions and infrastructure for modern business needs.',
|
||||
'Data Analytics': 'Advanced analytics and business intelligence for data-driven insights and strategic decision making.',
|
||||
'Predictive': 'Predictive modeling and forecasting to anticipate trends, behaviors, and future outcomes.',
|
||||
'Media & Entertainment': 'Content creation, recommendation systems, and audience engagement optimization.',
|
||||
'Supply Chain': 'AI-driven supply chain optimization, demand forecasting, and inventory management solutions.',
|
||||
'Risk Mgmt': 'Advanced risk assessment and management systems using AI to identify, analyze, and mitigate business risks.'
|
||||
};
|
||||
|
||||
return tooltipMap[nodeText] || 'Advanced AI technology solutions for modern business challenges.';
|
||||
}
|
||||
|
||||
showTooltip(node, event) {
|
||||
const tooltip = this.tooltips.get(node);
|
||||
if (!tooltip) return;
|
||||
|
||||
tooltip.style.opacity = '1';
|
||||
tooltip.style.visibility = 'visible';
|
||||
tooltip.style.pointerEvents = 'none';
|
||||
this.updateTooltipPosition(node, event);
|
||||
}
|
||||
|
||||
updateTooltipPosition(node, event) {
|
||||
const tooltip = this.tooltips.get(node);
|
||||
if (!tooltip) return;
|
||||
|
||||
const tooltipRect = tooltip.getBoundingClientRect();
|
||||
const viewport = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
|
||||
let left = event.clientX + 15;
|
||||
let top = event.clientY - tooltipRect.height - 10;
|
||||
|
||||
// Adjust if tooltip goes outside viewport
|
||||
if (left + tooltipRect.width > viewport.width - 10) {
|
||||
left = event.clientX - tooltipRect.width - 15;
|
||||
}
|
||||
|
||||
if (top < 10) {
|
||||
top = event.clientY + 15;
|
||||
}
|
||||
|
||||
tooltip.style.left = `${left}px`;
|
||||
tooltip.style.top = `${top}px`;
|
||||
}
|
||||
|
||||
hideTooltip(node) {
|
||||
const tooltip = this.tooltips.get(node);
|
||||
if (!tooltip) return;
|
||||
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.visibility = 'hidden';
|
||||
}
|
||||
setupHoverEffects(node) {
|
||||
// Get all data nodes from both containers for the dimming effect
|
||||
const allDataNodes = [
|
||||
...document.querySelectorAll('.neural-network-container .data-node'),
|
||||
...document.querySelectorAll('.hero-animation .data-node')
|
||||
];
|
||||
|
||||
node.addEventListener('mouseenter', () => {
|
||||
// Enhanced scaling and glow
|
||||
node.style.transform = 'scale(1.15)';
|
||||
node.style.filter = 'brightness(1.2) drop-shadow(0 0 15px rgba(59, 130, 246, 0.6))';
|
||||
node.style.zIndex = '100';
|
||||
|
||||
// Enhanced icon effects
|
||||
const nodeIcon = node.querySelector('.node-icon img');
|
||||
if (nodeIcon) {
|
||||
nodeIcon.style.transform = 'scale(1.1)';
|
||||
nodeIcon.style.filter = 'brightness(1.3) saturate(1.2)';
|
||||
}
|
||||
// Enhanced text effects
|
||||
const nodeText = node.querySelector('.node-text') || node.querySelector('.node-label');
|
||||
if (nodeText) {
|
||||
nodeText.style.color = '#60a5fa';
|
||||
nodeText.style.textShadow = '0 0 8px rgba(96, 165, 250, 0.5)';
|
||||
nodeText.style.transform = 'scale(1.05)';
|
||||
}
|
||||
|
||||
// Enhanced core glow
|
||||
const nodeCore = node.querySelector('.node-core');
|
||||
if (nodeCore) {
|
||||
nodeCore.style.boxShadow = '0 0 20px rgba(59, 130, 246, 0.8), inset 0 0 20px rgba(139, 92, 246, 0.3)';
|
||||
nodeCore.style.background = 'radial-gradient(circle, rgba(59, 130, 246, 0.3), rgba(139, 92, 246, 0.2))';
|
||||
}
|
||||
// Dim other nodes for focus effect
|
||||
allDataNodes.forEach(otherNode => {
|
||||
if (otherNode !== node) {
|
||||
otherNode.style.opacity = '0.3';
|
||||
otherNode.style.filter = 'blur(1px)';
|
||||
}
|
||||
});
|
||||
|
||||
// Create ripple effect
|
||||
this.createRippleEffect(node);
|
||||
});
|
||||
|
||||
node.addEventListener('mouseleave', () => {
|
||||
// Reset node
|
||||
node.style.transform = '';
|
||||
node.style.filter = '';
|
||||
node.style.zIndex = '';
|
||||
|
||||
// Reset icon
|
||||
const nodeIcon = node.querySelector('.node-icon img');
|
||||
if (nodeIcon) {
|
||||
nodeIcon.style.transform = '';
|
||||
nodeIcon.style.filter = '';
|
||||
}
|
||||
// Reset text
|
||||
const nodeText = node.querySelector('.node-text') || node.querySelector('.node-label');
|
||||
if (nodeText) {
|
||||
nodeText.style.color = '';
|
||||
nodeText.style.textShadow = '';
|
||||
nodeText.style.transform = '';
|
||||
}
|
||||
|
||||
// Reset core
|
||||
const nodeCore = node.querySelector('.node-core');
|
||||
if (nodeCore) {
|
||||
nodeCore.style.boxShadow = '';
|
||||
nodeCore.style.background = '';
|
||||
}
|
||||
// Restore other nodes
|
||||
allDataNodes.forEach(otherNode => {
|
||||
otherNode.style.opacity = '';
|
||||
otherNode.style.filter = '';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createRippleEffect(node) {
|
||||
const ripple = document.createElement('div');
|
||||
const rect = node.getBoundingClientRect();
|
||||
const size = Math.max(rect.width, rect.height) * 2;
|
||||
|
||||
ripple.style.cssText = `
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(59, 130, 246, 0.3), transparent 70%);
|
||||
transform: scale(0);
|
||||
animation: rippleEffect 0.8s ease-out;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
ripple.style.width = `${size}px`;
|
||||
ripple.style.height = `${size}px`;
|
||||
ripple.style.left = `${rect.left + rect.width/2 - size/2}px`;
|
||||
ripple.style.top = `${rect.top + rect.height/2 - size/2}px`;
|
||||
|
||||
document.body.appendChild(ripple);
|
||||
|
||||
setTimeout(() => ripple.remove(), 800);
|
||||
}
|
||||
|
||||
setupClickEffects(node) {
|
||||
node.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Create particle burst effect
|
||||
this.createParticleBurst(node);
|
||||
|
||||
// Enhanced click feedback
|
||||
node.style.transform = 'scale(0.95)';
|
||||
setTimeout(() => {
|
||||
node.style.transform = 'scale(1.15)';
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
createParticleBurst(node) {
|
||||
const rect = node.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const particle = document.createElement('div');
|
||||
const angle = (i / 12) * Math.PI * 2;
|
||||
const velocity = 50 + Math.random() * 30;
|
||||
|
||||
particle.style.cssText = `
|
||||
position: fixed;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: linear-gradient(45deg, #3b82f6, #8b5cf6);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 1001;
|
||||
left: ${centerX}px;
|
||||
top: ${centerY}px;
|
||||
`;
|
||||
|
||||
document.body.appendChild(particle);
|
||||
|
||||
// Animate particle
|
||||
const endX = centerX + Math.cos(angle) * velocity;
|
||||
const endY = centerY + Math.sin(angle) * velocity;
|
||||
|
||||
particle.animate([
|
||||
{ transform: 'translate(0, 0) scale(1)', opacity: 1 },
|
||||
{ transform: `translate(${endX - centerX}px, ${endY - centerY}px) scale(0)`, opacity: 0 }
|
||||
], {
|
||||
duration: 800,
|
||||
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||
}).onfinish = () => particle.remove();
|
||||
}
|
||||
}
|
||||
|
||||
addStyles() {
|
||||
if (document.getElementById('data-node-styles')) return;
|
||||
|
||||
const styles = document.createElement('style');
|
||||
styles.id = 'data-node-styles';
|
||||
styles.textContent = `
|
||||
/* Data Node Tooltip Styles */
|
||||
.data-node-tooltip {
|
||||
position: fixed;
|
||||
background: linear-gradient(135deg, rgba(15, 23, 42, 0.95), rgba(30, 41, 59, 0.95));
|
||||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||
border-radius: 12px;
|
||||
padding: 12px 16px;
|
||||
max-width: 280px;
|
||||
font-size: 14px;
|
||||
color: #e2e8f0;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 10000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip-header {
|
||||
font-weight: 600;
|
||||
color: #60a5fa;
|
||||
margin-bottom: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
line-height: 1.4;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
/* Ripple Effect Animation */
|
||||
@keyframes rippleEffect {
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhanced Data Node Interactions */
|
||||
.data-node {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.data-node:hover {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Enhanced neural network container interactions */
|
||||
.neural-network-container .data-node {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.neural-network-container .data-node:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* Enhanced hero animation data nodes */
|
||||
.hero-animation .data-node {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.hero-animation .data-node:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* Neural Network Canvas Animation Styles */
|
||||
@keyframes neuralRipple {
|
||||
to {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes neuralPulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes neuralTrailFade {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(styles);
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse Trail Effect for Neural Network
|
||||
function createNeuralTrail(x, y) {
|
||||
const trail = document.createElement('div');
|
||||
trail.style.cssText = `
|
||||
position: fixed;
|
||||
left: ${x}px;
|
||||
top: ${y}px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: radial-gradient(circle, rgba(59, 130, 246, 0.8), transparent);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
animation: neuralTrailFade 1.5s ease-out forwards;
|
||||
`;
|
||||
|
||||
document.body.appendChild(trail);
|
||||
setTimeout(() => trail.remove(), 1500);
|
||||
}
|
||||
|
||||
// Global mouse trail for hero section
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
const heroSection = document.querySelector('#hero');
|
||||
if (!heroSection) return;
|
||||
|
||||
const rect = heroSection.getBoundingClientRect();
|
||||
if (e.clientY >= rect.top && e.clientY <= rect.bottom) {
|
||||
if (Math.random() < 0.1) { // Reduced frequency
|
||||
createNeuralTrail(e.clientX, e.clientY);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize everything when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Add a small delay to ensure all elements are fully rendered
|
||||
setTimeout(() => {
|
||||
console.log('Initializing unified neural network and data node interactions...');
|
||||
|
||||
// Debug: Check what data nodes exist
|
||||
console.log('=== DEBUG: Checking data node structure ===');
|
||||
const neuralNodes = document.querySelectorAll('.neural-network-container .data-node');
|
||||
const heroNodes = document.querySelectorAll('.hero-animation .data-node');
|
||||
const allNodes = document.querySelectorAll('.data-node');
|
||||
|
||||
console.log(`Total .data-node elements found: ${allNodes.length}`);
|
||||
console.log(`Neural network container nodes: ${neuralNodes.length}`);
|
||||
console.log(`Hero animation nodes: ${heroNodes.length}`);
|
||||
|
||||
// Log each node's details
|
||||
allNodes.forEach((node, index) => {
|
||||
const label = node.querySelector('.node-label')?.textContent?.trim() || 'No label';
|
||||
const text = node.querySelector('.node-text')?.textContent?.trim() || 'No text';
|
||||
const container = node.closest('.neural-network-container') ? 'neural-network' :
|
||||
node.closest('.hero-animation') ? 'hero-animation' : 'other';
|
||||
console.log(`Node ${index + 1}: "${label}" (text: "${text}") in ${container}`);
|
||||
});
|
||||
console.log('=== END DEBUG ===');
|
||||
|
||||
// Initialize neural network animation
|
||||
const neuralAnimation = new NeuralNetworkAnimation();
|
||||
|
||||
// Initialize data node interactions
|
||||
const dataNodeInteractions = new DataNodeInteractions();
|
||||
|
||||
// Handle resize for neural network
|
||||
window.addEventListener('resize', () => {
|
||||
neuralAnimation.setupCanvas();
|
||||
neuralAnimation.createNodes();
|
||||
});
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener('beforeunload', () => {
|
||||
neuralAnimation.destroy();
|
||||
});
|
||||
|
||||
console.log('Unified system initialized successfully');
|
||||
}, 100); // Small delay to ensure DOM is fully rendered
|
||||
});
|
||||
|
||||
// Immediate test function to verify script loading
|
||||
console.log('🚀 Unified Neural Interactions script loaded!');
|
||||
|
||||
// Test function to manually check data nodes
|
||||
window.testDataNodes = function() {
|
||||
const allNodes = document.querySelectorAll('.data-node');
|
||||
console.log(`Found ${allNodes.length} data nodes:`);
|
||||
allNodes.forEach((node, i) => {
|
||||
const label = node.querySelector('.node-label')?.textContent || 'No label';
|
||||
console.log(`${i + 1}. ${label}`);
|
||||
});
|
||||
return allNodes;
|
||||
};
|
||||
|
||||
// Export for potential use in other modules
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { NeuralNetworkAnimation, DataNodeInteractions };
|
||||
}
|
||||
Reference in New Issue
Block a user