403 Zeilen
13 KiB
JavaScript
403 Zeilen
13 KiB
JavaScript
/**
|
|
* Animation module for IntelSight website
|
|
* Contains all animation logic and visual effects
|
|
*/
|
|
|
|
// Particle Animation System
|
|
const ParticleAnimation = {
|
|
canvas: null,
|
|
ctx: null,
|
|
particles: [],
|
|
|
|
/**
|
|
* Initialize particle animation
|
|
*/
|
|
init() {
|
|
this.canvas = document.querySelector(SELECTORS.PARTICLE_CANVAS);
|
|
if (!this.canvas) return;
|
|
|
|
this.ctx = this.canvas.getContext('2d');
|
|
this.resizeCanvas();
|
|
this.createParticles();
|
|
this.animate();
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', () => this.resizeCanvas());
|
|
},
|
|
|
|
/**
|
|
* Resize canvas to window size
|
|
*/
|
|
resizeCanvas() {
|
|
this.canvas.width = window.innerWidth;
|
|
this.canvas.height = window.innerHeight;
|
|
},
|
|
|
|
/**
|
|
* Create particle objects
|
|
*/
|
|
createParticles() {
|
|
this.particles = [];
|
|
for (let i = 0; i < CONFIG.ANIMATION.PARTICLE_COUNT; i++) {
|
|
this.particles.push(new Particle(this.canvas));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Main animation loop
|
|
*/
|
|
animate() {
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
// Update and draw particles
|
|
this.particles.forEach(particle => {
|
|
particle.update(this.canvas);
|
|
particle.draw(this.ctx);
|
|
});
|
|
|
|
// Draw connections between particles
|
|
this.drawConnections();
|
|
|
|
requestAnimationFrame(() => this.animate());
|
|
},
|
|
|
|
/**
|
|
* Draw connections between nearby particles
|
|
*/
|
|
drawConnections() {
|
|
for (let a = 0; a < this.particles.length; a++) {
|
|
for (let b = a + 1; b < this.particles.length; b++) {
|
|
const distance = Math.sqrt(
|
|
Math.pow(this.particles[a].x - this.particles[b].x, 2) +
|
|
Math.pow(this.particles[a].y - this.particles[b].y, 2)
|
|
);
|
|
|
|
if (distance < CONFIG.ANIMATION.CONNECTION_DISTANCE) {
|
|
const opacity = 0.15 * (1 - distance / CONFIG.ANIMATION.CONNECTION_DISTANCE);
|
|
// Use darker blue for better visibility on light background
|
|
this.ctx.strokeStyle = `rgba(15, 114, 181, ${opacity})`;
|
|
this.ctx.lineWidth = 1;
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.particles[a].x, this.particles[a].y);
|
|
this.ctx.lineTo(this.particles[b].x, this.particles[b].y);
|
|
this.ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Particle class for animation
|
|
*/
|
|
class Particle {
|
|
constructor(canvas) {
|
|
this.x = Math.random() * canvas.width;
|
|
this.y = Math.random() * canvas.height;
|
|
this.size = Math.random() * (CONFIG.ANIMATION.PARTICLE_SIZE_MAX - CONFIG.ANIMATION.PARTICLE_SIZE_MIN) + CONFIG.ANIMATION.PARTICLE_SIZE_MIN;
|
|
this.speedX = (Math.random() - 0.5) * CONFIG.ANIMATION.PARTICLE_SPEED;
|
|
this.speedY = (Math.random() - 0.5) * CONFIG.ANIMATION.PARTICLE_SPEED;
|
|
this.opacity = Math.random() * 0.5 + 0.2;
|
|
}
|
|
|
|
update(canvas) {
|
|
this.x += this.speedX;
|
|
this.y += this.speedY;
|
|
|
|
// Wrap around screen edges
|
|
if (this.x > canvas.width) this.x = 0;
|
|
else if (this.x < 0) this.x = canvas.width;
|
|
|
|
if (this.y > canvas.height) this.y = 0;
|
|
else if (this.y < 0) this.y = canvas.height;
|
|
}
|
|
|
|
draw(ctx) {
|
|
// Use darker blue for better visibility on light background
|
|
ctx.fillStyle = `rgba(15, 114, 181, ${this.opacity * 0.7})`;
|
|
ctx.beginPath();
|
|
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
// Counter Animation
|
|
const CounterAnimation = {
|
|
/**
|
|
* Animate all counter elements
|
|
*/
|
|
animateAll() {
|
|
const counters = document.querySelectorAll(SELECTORS.INDICATOR_VALUE);
|
|
counters.forEach(counter => this.animateCounter(counter));
|
|
},
|
|
|
|
/**
|
|
* Animate a single counter
|
|
* @param {HTMLElement} counter - Counter element to animate
|
|
*/
|
|
animateCounter(counter) {
|
|
const target = parseFloat(counter.getAttribute(DATA_ATTRS.TARGET));
|
|
const increment = target / CONFIG.ANIMATION.COUNTER_SPEED;
|
|
let current = 0;
|
|
|
|
const updateCounter = () => {
|
|
current += increment;
|
|
|
|
if (current < target) {
|
|
counter.innerText = Math.ceil(current);
|
|
setTimeout(updateCounter, CONFIG.TIMING.COUNTER_UPDATE_INTERVAL);
|
|
} else {
|
|
// Set final value with proper formatting
|
|
if (target === CONFIG.TRUST_INDICATORS.AVAILABILITY) {
|
|
counter.innerText = target + '%';
|
|
} else if (target === CONFIG.TRUST_INDICATORS.AUTHORITIES_COUNT) {
|
|
counter.innerText = target + '+';
|
|
} else if (target === CONFIG.TRUST_INDICATORS.SUPPORT_HOURS) {
|
|
counter.innerText = target + '/7';
|
|
}
|
|
}
|
|
};
|
|
|
|
updateCounter();
|
|
}
|
|
};
|
|
|
|
// Scroll Animations
|
|
const ScrollAnimations = {
|
|
scrollIndicator: null,
|
|
|
|
/**
|
|
* Initialize scroll-based animations
|
|
*/
|
|
init() {
|
|
this.scrollIndicator = document.querySelector(SELECTORS.SCROLL_INDICATOR);
|
|
this.setupScrollIndicator();
|
|
this.setupIntersectionObserver();
|
|
},
|
|
|
|
/**
|
|
* Setup scroll indicator behavior
|
|
*/
|
|
setupScrollIndicator() {
|
|
if (!this.scrollIndicator) return;
|
|
|
|
// Click to scroll to about section
|
|
this.scrollIndicator.addEventListener('click', () => {
|
|
const aboutSection = document.querySelector('#about');
|
|
if (aboutSection) {
|
|
aboutSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
});
|
|
|
|
// Hide/show based on scroll position
|
|
let scrollTimeout;
|
|
window.addEventListener('scroll', () => {
|
|
const hero = document.querySelector(SELECTORS.HERO);
|
|
|
|
if (window.scrollY > CONFIG.ANIMATION.SCROLL_THRESHOLD) {
|
|
if (hero) hero.classList.add(CLASSES.SCROLLED);
|
|
if (this.scrollIndicator) this.scrollIndicator.style.opacity = '0';
|
|
} else {
|
|
if (hero) hero.classList.remove(CLASSES.SCROLLED);
|
|
if (this.scrollIndicator) this.scrollIndicator.style.opacity = '1';
|
|
}
|
|
|
|
clearTimeout(scrollTimeout);
|
|
scrollTimeout = setTimeout(() => {
|
|
if (window.scrollY > CONFIG.ANIMATION.SCROLL_THRESHOLD && this.scrollIndicator) {
|
|
this.scrollIndicator.style.display = 'none';
|
|
} else if (this.scrollIndicator) {
|
|
this.scrollIndicator.style.display = 'flex';
|
|
}
|
|
}, CONFIG.TIMING.SCROLL_HIDE_DELAY);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Setup intersection observer for scroll-triggered animations
|
|
*/
|
|
setupIntersectionObserver() {
|
|
const observerOptions = {
|
|
threshold: CONFIG.OBSERVER.THRESHOLD,
|
|
rootMargin: CONFIG.OBSERVER.ROOT_MARGIN
|
|
};
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
// Trust indicators animation
|
|
if (entry.target.classList.contains('trust-indicators')) {
|
|
CounterAnimation.animateAll();
|
|
observer.unobserve(entry.target);
|
|
}
|
|
|
|
// Timeline animation
|
|
if (entry.target.classList.contains('timeline')) {
|
|
const items = entry.target.querySelectorAll('.timeline-item');
|
|
items.forEach((item, index) => {
|
|
setTimeout(() => {
|
|
item.classList.add(CLASSES.VISIBLE);
|
|
}, index * 300);
|
|
});
|
|
observer.unobserve(entry.target);
|
|
}
|
|
|
|
// Feature nodes animation
|
|
if (entry.target.classList.contains('feature-nodes')) {
|
|
const nodes = entry.target.querySelectorAll('.node');
|
|
nodes.forEach((node, index) => {
|
|
setTimeout(() => {
|
|
node.style.opacity = '1';
|
|
node.style.transform = 'translateY(0)';
|
|
}, index * 150);
|
|
});
|
|
observer.unobserve(entry.target);
|
|
}
|
|
}
|
|
});
|
|
}, observerOptions);
|
|
|
|
// Observe elements
|
|
const trustIndicators = document.querySelector(SELECTORS.TRUST_INDICATORS);
|
|
if (trustIndicators) {
|
|
trustIndicators.style.opacity = '0';
|
|
observer.observe(trustIndicators);
|
|
}
|
|
|
|
const timeline = document.querySelector('.timeline');
|
|
if (timeline) observer.observe(timeline);
|
|
|
|
const featureNodes = document.querySelector('.feature-nodes');
|
|
if (featureNodes) {
|
|
document.querySelectorAll('.node').forEach(node => {
|
|
node.style.opacity = '0';
|
|
node.style.transform = 'translateY(30px)';
|
|
node.style.transition = 'all 0.6s ease';
|
|
});
|
|
observer.observe(featureNodes);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Glitch Effect
|
|
const GlitchEffect = {
|
|
/**
|
|
* Apply glitch effect to element on hover
|
|
* @param {HTMLElement} element - Element to apply effect to
|
|
*/
|
|
apply(element) {
|
|
if (!element) return;
|
|
|
|
let glitchInterval;
|
|
element.addEventListener('mouseenter', () => {
|
|
let count = 0;
|
|
glitchInterval = setInterval(() => {
|
|
element.style.textShadow = `
|
|
${Math.random() * 5}px ${Math.random() * 5}px 0 rgba(0, 212, 255, 0.5),
|
|
${Math.random() * -5}px ${Math.random() * 5}px 0 rgba(255, 0, 128, 0.5)
|
|
`;
|
|
count++;
|
|
if (count > CONFIG.ANIMATION.GLITCH_ITERATIONS) {
|
|
clearInterval(glitchInterval);
|
|
element.style.textShadow = 'none';
|
|
}
|
|
}, CONFIG.ANIMATION.GLITCH_INTERVAL);
|
|
});
|
|
}
|
|
};
|
|
|
|
// Interactive Elements
|
|
const InteractiveElements = {
|
|
/**
|
|
* Initialize all interactive element animations
|
|
*/
|
|
init() {
|
|
this.setupNodeHoverEffects();
|
|
this.setupWidgetHoverEffects();
|
|
this.setupInteractiveIcon();
|
|
},
|
|
|
|
/**
|
|
* Setup hover effects for node elements
|
|
*/
|
|
setupNodeHoverEffects() {
|
|
document.querySelectorAll('.node').forEach(node => {
|
|
node.addEventListener('mouseenter', function() {
|
|
const icon = this.querySelector('.node-icon');
|
|
if (icon) icon.style.transform = 'scale(1.2) rotate(5deg)';
|
|
});
|
|
|
|
node.addEventListener('mouseleave', function() {
|
|
const icon = this.querySelector('.node-icon');
|
|
if (icon) icon.style.transform = 'scale(1) rotate(0deg)';
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Setup hover effects for widget elements
|
|
*/
|
|
setupWidgetHoverEffects() {
|
|
document.querySelectorAll('.widget').forEach(widget => {
|
|
widget.addEventListener('mouseenter', function() {
|
|
this.style.boxShadow = '0 5px 20px rgba(0, 212, 255, 0.3)';
|
|
});
|
|
|
|
widget.addEventListener('mouseleave', function() {
|
|
this.style.boxShadow = 'none';
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Setup 3D interactive icon effect
|
|
*/
|
|
setupInteractiveIcon() {
|
|
const icon = document.querySelector(SELECTORS.INTERACTIVE_ICON);
|
|
if (!icon) return;
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
const rect = icon.getBoundingClientRect();
|
|
const centerX = rect.left + rect.width / 2;
|
|
const centerY = rect.top + rect.height / 2;
|
|
|
|
const mouseX = (e.clientX - centerX) / 20;
|
|
const mouseY = (e.clientY - centerY) / 20;
|
|
|
|
icon.style.transform = `perspective(1000px) rotateY(${mouseX}deg) rotateX(${-mouseY}deg)`;
|
|
});
|
|
}
|
|
};
|
|
|
|
// Initialize all animations
|
|
const Animations = {
|
|
/**
|
|
* Initialize all animation systems
|
|
*/
|
|
init() {
|
|
// Core animations
|
|
ParticleAnimation.init();
|
|
ScrollAnimations.init();
|
|
InteractiveElements.init();
|
|
|
|
// Apply glitch effect to main title
|
|
const mainTitle = document.querySelector('.main-title');
|
|
if (mainTitle) {
|
|
GlitchEffect.apply(mainTitle);
|
|
}
|
|
|
|
// Page load animations
|
|
window.addEventListener('load', () => {
|
|
document.body.classList.add(CLASSES.LOADED);
|
|
|
|
// Fade in hero content
|
|
setTimeout(() => {
|
|
const heroContent = document.querySelector(SELECTORS.HERO_CONTENT);
|
|
if (heroContent) {
|
|
heroContent.style.opacity = '1';
|
|
heroContent.style.transform = 'translateY(0)';
|
|
}
|
|
}, 100);
|
|
});
|
|
}
|
|
}; |