(() => { const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches; document.getElementById('year').textContent = new Date().getFullYear(); document.getElementById('ticket-number').textContent = `TKT-${String(Math.floor(1000 + Math.random() * 9000))}`; document.getElementById('started-at').value = Math.floor(Date.now() / 1000); const reveals = document.querySelectorAll('.reveal'); if (reducedMotion || !('IntersectionObserver' in window)) { reveals.forEach((item) => item.classList.add('visible')); } else { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.12 }); reveals.forEach((item) => observer.observe(item)); } const form = document.getElementById('contact-form'); const status = document.getElementById('form-status'); form.addEventListener('submit', async (event) => { event.preventDefault(); if (!form.reportValidity()) return; form.classList.remove('error', 'success'); form.classList.add('sending'); status.textContent = 'Routing your ticket…'; form.querySelector('.button-label').textContent = 'Sending'; try { const response = await fetch(form.action, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } }); const result = await response.json(); if (!response.ok || !result.ok) throw new Error(result.message || 'Message could not be sent.'); form.classList.add('success'); status.textContent = result.message; form.reset(); document.getElementById('started-at').value = Math.floor(Date.now() / 1000); form.querySelector('.button-label').textContent = 'Ticket received ✓'; } catch (error) { form.classList.add('error'); status.textContent = error.message; form.querySelector('.button-label').textContent = 'Try again'; } finally { form.classList.remove('sending'); } }); if (reducedMotion) return; const canvas = document.getElementById('particle-field'); const context = canvas.getContext('2d'); const pointer = { x: -1000, y: -1000 }; let particles = []; let width = 0; let height = 0; const resize = () => { const ratio = Math.min(devicePixelRatio || 1, 2); width = innerWidth; height = innerHeight; canvas.width = width * ratio; canvas.height = height * ratio; canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; context.setTransform(ratio, 0, 0, ratio, 0, 0); particles = Array.from({ length: Math.min(55, Math.floor(width / 24)) }, () => ({ x: Math.random() * width, y: Math.random() * height, vx: (Math.random() - .5) * .18, vy: (Math.random() - .5) * .18, r: Math.random() * 1.4 + .4 })); }; addEventListener('resize', resize, { passive: true }); addEventListener('pointermove', (event) => { pointer.x = event.clientX; pointer.y = event.clientY; }, { passive: true }); addEventListener('pointerleave', () => { pointer.x = pointer.y = -1000; }); resize(); const draw = () => { context.clearRect(0, 0, width, height); particles.forEach((particle, index) => { particle.x += particle.vx; particle.y += particle.vy; if (particle.x < 0 || particle.x > width) particle.vx *= -1; if (particle.y < 0 || particle.y > height) particle.vy *= -1; const pdx = pointer.x - particle.x, pdy = pointer.y - particle.y, pointerDistance = Math.hypot(pdx, pdy); if (pointerDistance < 130) { particle.x -= pdx * .0008; particle.y -= pdy * .0008; } context.beginPath(); context.arc(particle.x, particle.y, particle.r, 0, Math.PI * 2); context.fillStyle = '#6fc9ff55'; context.fill(); for (let j = index + 1; j < particles.length; j++) { const other = particles[j], distance = Math.hypot(particle.x - other.x, particle.y - other.y); if (distance < 115) { context.beginPath(); context.moveTo(particle.x, particle.y); context.lineTo(other.x, other.y); context.strokeStyle = `rgba(70,185,255,${(1 - distance / 115) * .09})`; context.stroke(); } } }); requestAnimationFrame(draw); }; draw(); })();