Spaces:
Running
Running
| // Creative Studio Animation for Nexus | |
| class CreativeParticleAnimation { | |
| constructor(canvas) { | |
| this.canvas = canvas; | |
| this.ctx = canvas.getContext('2d'); | |
| this.particles = []; | |
| this.flowField = []; | |
| this.mouse = { x: 0, y: 0, radius: 200 }; | |
| this.time = 0; | |
| this.init(); | |
| this.animate(); | |
| this.setupEventListeners(); | |
| } | |
| init() { | |
| this.resizeCanvas(); | |
| this.createFlowField(); | |
| // Create creative particles with artistic properties | |
| const particleCount = Math.min(80, Math.floor(window.innerWidth / 15)); | |
| for (let i = 0; i < particleCount; i++) { | |
| this.particles.push({ | |
| x: Math.random() * this.canvas.width, | |
| y: Math.random() * this.canvas.height, | |
| vx: (Math.random() - 0.5) * 3, | |
| vy: (Math.random() - 0.5) * 3, | |
| radius: Math.random() * 4 + 1, | |
| hue: Math.random() * 360, | |
| saturation: 70 + Math.random() * 30, | |
| lightness: 50 + Math.random() * 30, | |
| life: Math.random() * 100, | |
| maxLife: 100 + Math.random() * 50 | |
| }); | |
| } | |
| } | |
| createFlowField() { | |
| const cols = Math.floor(this.canvas.width / 50); | |
| const rows = Math.floor(this.canvas.height / 50); | |
| for (let x = 0; x < cols; x++) { | |
| this.flowField[x] = []; | |
| for (let y = 0; y < rows; y++) { | |
| const angle = Math.sin(x * 0.01 + this.time * 0.001) * Math.cos(y * 0.01 + this.time * 0.001); | |
| this.flowField[x][y] = { | |
| angle: angle, | |
| strength: Math.sin(angle) * 2 | |
| }; | |
| } | |
| } | |
| } | |
| resizeCanvas() { | |
| this.canvas.width = window.innerWidth; | |
| this.canvas.height = window.innerHeight; | |
| } | |
| setupEventListeners() { | |
| window.addEventListener('resize', () => this.resizeCanvas()); | |
| this.canvas.addEventListener('mousemove', (e) => { | |
| this.mouse.x = e.clientX; | |
| this.mouse.y = e.clientY; | |
| }); | |
| this.canvas.addEventListener('mouseleave', () => { | |
| this.mouse.x = undefined; | |
| this.mouse.y = undefined; | |
| }); | |
| } | |
| animate() { | |
| requestAnimationFrame(() => this.animate()); | |
| // Create trailing effect | |
| this.ctx.fillStyle = 'rgba(17, 24, 39, 0.1)'; | |
| this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); | |
| this.time += 1; | |
| this.updateParticles(); | |
| this.drawParticleTrails(); | |
| this.drawConnections(); | |
| this.drawArtisticElements(); | |
| } | |
| updateParticles() { | |
| this.particles.forEach((particle, index) => { | |
| // Flow field influence | |
| const col = Math.floor(particle.x / 50); | |
| const row = Math.floor(particle.y / 50); | |
| if (this.flowField[col] && this.flowField[col][row]) { | |
| const field = this.flowField[col][row]; | |
| particle.vx += Math.cos(field.angle) * field.strength * 0.1; | |
| particle.vy += Math.sin(field.angle) * field.strength * 0.1; | |
| } | |
| // Mouse interaction - attract particles | |
| if (this.mouse.x && this.mouse.y) { | |
| const dx = this.mouse.x - particle.x; | |
| const dy = this.mouse.y - particle.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < this.mouse.radius) { | |
| const force = (this.mouse.radius - distance) / this.mouse.radius; | |
| const angle = Math.atan2(dy, dx); | |
| particle.vx += Math.cos(angle) * force * 0.8; | |
| particle.vy += Math.sin(angle) * force * 0.8; | |
| // Color shift on interaction | |
| particle.hue = (particle.hue + force * 20) % 360; | |
| } | |
| } | |
| // Natural movement | |
| particle.x += particle.vx; | |
| particle.y += particle.vy; | |
| particle.vx *= 0.98; | |
| particle.vy *= 0.98; | |
| // Boundary bounce with creative wrapping | |
| if (particle.x < 0 || particle.x > this.canvas.width) particle.vx *= -0.8; | |
| if (particle.y < 0 || particle.y > this.canvas.height) particle.vy *= -0.8; | |
| // Keep particles in bounds | |
| particle.x = Math.max(0, Math.min(this.canvas.width, particle.x)); | |
| particle.y = Math.max(0, Math.min(this.canvas.height, particle.y)); | |
| // Particle life cycle | |
| particle.life += 1; | |
| if (particle.life > particle.maxLife) { | |
| // Respawn with new properties | |
| particle.x = Math.random() * this.canvas.width; | |
| particle.y = Math.random() * this.canvas.height; | |
| particle.life = 0; | |
| particle.hue = Math.random() * 360; | |
| } | |
| }); | |
| } | |
| drawParticleTrails() { | |
| this.particles.forEach(particle => { | |
| // Draw particle trail | |
| this.ctx.beginPath(); | |
| this.ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2); | |
| const alpha = (particle.maxLife - particle.life) / particle.maxLife; | |
| const color = `hsla(${particle.hue}, ${particle.saturation}%, ${particle.lightness}%, ${alpha})`; | |
| this.ctx.fillStyle = color; | |
| this.ctx.fill(); | |
| // Add glow effect | |
| this.ctx.shadowColor = color; | |
| this.ctx.shadowBlur = 10; | |
| this.ctx.fill(); | |
| this.ctx.shadowBlur = 0; | |
| }); | |
| } | |
| drawConnections() { | |
| for (let i = 0; i < this.particles.length; i++) { | |
| for (let j = i + 1; j < this.particles.length; j++) { | |
| const dx = this.particles[i].x - this.particles[j].x; | |
| const dy = this.particles[i].y - this.particles[j].y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < 120) { | |
| const opacity = 1 - (distance / 120); | |
| const hue = (this.particles[i].hue + this.particles[j].hue) / 2; | |
| this.ctx.strokeStyle = `hsla(${hue}, 80%, 60%, ${opacity * 0.3})`; | |
| this.ctx.lineWidth = 1; | |
| this.ctx.beginPath(); | |
| this.ctx.moveTo(this.particles[i].x, this.particles[i].y); | |
| this.ctx.lineTo(this.particles[j].x, this.particles[j].y); | |
| this.ctx.stroke(); | |
| } | |
| } | |
| } | |
| } | |
| drawArtisticElements() { | |
| // Draw abstract geometric shapes | |
| if (this.time % 300 === 0) { | |
| this.drawGeometricShape(); | |
| } | |
| } | |
| drawGeometricShape() { | |
| const centerX = this.canvas.width / 2; | |
| const centerY = this.canvas.height / 2; | |
| const size = 50 + Math.sin(this.time * 0.01) * 30; | |
| this.ctx.save(); | |
| this.ctx.translate(centerX, centerY); | |
| this.ctx.rotate(this.time * 0.001); | |
| // Draw rotating polygon | |
| this.ctx.beginPath(); | |
| for (let i = 0; i < 6; i++) { | |
| const angle = (i / 6) * Math.PI * 2; | |
| const x = Math.cos(angle) * size; | |
| const y = Math.sin(angle) * size; | |
| if (i === 0) this.ctx.moveTo(x, y); | |
| else this.ctx.lineTo(x, y); | |
| } | |
| this.ctx.closePath(); | |
| this.ctx.strokeStyle = `hsla(${(this.time * 0.5) % 360}, 70%, 70%, 0.5)`; | |
| this.ctx.lineWidth = 2; | |
| this.ctx.stroke(); | |
| this.ctx.restore(); | |
| } | |
| } | |
| // Initialize when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const canvas = document.getElementById('networkCanvas'); | |
| if (canvas) { | |
| new CreativeParticleAnimation(canvas); | |
| } | |
| // Add interactive gallery effects | |
| addGalleryInteractions(); | |
| }); | |
| // Gallery interaction effects | |
| function addGalleryInteractions() { | |
| const galleryItems = document.querySelectorAll('.grid > div'); | |
| galleryItems.forEach((item, index) => { | |
| item.addEventListener('mouseenter', function() { | |
| this.style.transform = 'scale(1.05) rotate(2deg)'; | |
| this.style.zIndex = '10'; | |
| }); | |
| item.addEventListener('mouseleave', function() { | |
| this.style.transform = 'scale(1) rotate(0deg)'; | |
| this.style.zIndex = '1'; | |
| }); | |
| // Add click effect | |
| item.addEventListener('click', function() { | |
| this.classList.add('animate-pulse'); | |
| setTimeout(() => { | |
| this.classList.remove('animate-pulse'); | |
| }, 1000); | |
| }); | |
| }); | |
| } | |
| // Smooth scrolling for anchor links | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| const target = document.querySelector(this.getAttribute('href')); | |
| if (target) { | |
| target.scrollIntoView({ | |
| behavior: 'smooth', | |
| block: 'start' | |
| }); | |
| } | |
| }); | |
| }); | |
| // Add scroll-based animations | |
| window.addEventListener('scroll', function() { | |
| const scrolled = window.pageYOffset; | |
| const parallax = document.querySelector('#networkCanvas'); | |
| if (parallax) { | |
| parallax.style.transform = `translateY(${scrolled * 0.5}px)`; | |
| } | |
| }); | |