/** * 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); }); } };