| ```javascript |
| // Initialize particles |
| function initParticles() { |
| const particleHost = document.getElementById('particles'); |
| const VW = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); |
| const VH = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0); |
| const BASE = 12; |
| const density = Math.round((VW * VH) / (900 * 900)); |
| const PARTICLE_COUNT = Math.max(8, Math.min(22, BASE + density * 4)); |
| |
| const make = (t, c) => { |
| const e = document.createElement(t); |
| if (c) e.className = c; |
| return e; |
| }; |
| |
| const rand = (a, b) => Math.random() * (b - a) + a; |
| |
| for (let i = 0; i < PARTICLE_COUNT; i++) { |
| const p = make('div', 'particle'); |
| const size = rand(2, 6); |
| p.style.width = p.style.height = size + 'px'; |
| p.style.left = rand(0, 100) + '%'; |
| p.style.top = rand(0, 100) + '%'; |
| p.style.animationDelay = rand(0, 5) + 's'; |
| const dur = (4 + Math.random() * 3).toFixed(2) + 's'; |
| p.style.animationDuration = dur; |
| particleHost.appendChild(p); |
| } |
| } |
| |
| // Initialize intersection observer for reveals |
| function initReveals() { |
| const io = new IntersectionObserver((entries) => { |
| entries.forEach(e => { |
| if (e.isIntersecting) { |
| e.target.classList.add('in-view'); |
| io.unobserve(e.target); |
| } |
| }); |
| }, { root: null, rootMargin: '-100px', threshold: 0 }); |
| |
| document.querySelectorAll('.reveal-up, .reveal-left, .reveal-scale, .feature').forEach(el => io.observe(el)); |
| document.querySelectorAll('.feature').forEach((el, i) => { |
| el.style.transitionDelay = (0.6 + i * 0.1) + 's'; |
| }); |
| document.querySelectorAll('.reveal-up').forEach((el, i) => { |
| if (!el.style.transitionDelay) { |
| el.style.transitionDelay = (0.2 + i * 0.2) + 's'; |
| }); |
| } |
| |
| // Initialize parallax effect |
| function init |