/* Synergy — front-end interactions */ (function () { 'use strict'; var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; /* ---------- Animated starfield ---------- */ function initStars() { var canvas = document.getElementById('synergy-stars'); if (!canvas || reduceMotion) return; var ctx = canvas.getContext('2d'); var stars = []; var w, h, dpr; function resize() { dpr = Math.min(window.devicePixelRatio || 1, 2); w = canvas.width = window.innerWidth * dpr; h = canvas.height = window.innerHeight * dpr; canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px'; var count = Math.floor((window.innerWidth * window.innerHeight) / 6000); stars = []; for (var i = 0; i < count; i++) { stars.push({ x: Math.random() * w, y: Math.random() * h, r: (Math.random() * 1.3 + 0.2) * dpr, a: Math.random(), tw: Math.random() * 0.02 + 0.004, dir: Math.random() > 0.5 ? 1 : -1, gold: Math.random() > 0.82, vy: (Math.random() * 0.12 + 0.02) * dpr }); } } function draw() { ctx.clearRect(0, 0, w, h); for (var i = 0; i < stars.length; i++) { var s = stars[i]; s.a += s.tw * s.dir; if (s.a > 1) { s.a = 1; s.dir = -1; } else if (s.a < 0.1) { s.a = 0.1; s.dir = 1; } s.y += s.vy; if (s.y > h) { s.y = 0; s.x = Math.random() * w; } ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fillStyle = s.gold ? 'rgba(244, 228, 184, ' + s.a + ')' : 'rgba(235, 240, 255, ' + s.a + ')'; ctx.shadowBlur = s.gold ? 6 : 0; ctx.shadowColor = 'rgba(244, 228, 184, 0.6)'; ctx.fill(); } requestAnimationFrame(draw); } resize(); window.addEventListener('resize', resize); draw(); } /* ---------- Scroll reveal ---------- */ function initReveal() { var els = document.querySelectorAll('.reveal'); if (!('IntersectionObserver' in window) || reduceMotion) { els.forEach(function (el) { el.classList.add('is-visible'); }); return; } var io = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); } }); }, { threshold: 0.15, rootMargin: '0px 0px -8% 0px' }); els.forEach(function (el) { io.observe(el); }); } /* ---------- Header scrolled state ---------- */ function initHeader() { var header = document.getElementById('site-header'); if (!header) return; function onScroll() { header.classList.toggle('scrolled', window.scrollY > 40); } onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); } /* ---------- Mobile nav ---------- */ function initNav() { var toggle = document.querySelector('.nav-toggle'); var list = document.getElementById('primary-menu'); if (!toggle || !list) return; toggle.addEventListener('click', function () { var open = list.classList.toggle('open'); toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); }); list.querySelectorAll('a').forEach(function (a) { a.addEventListener('click', function () { list.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); }); }); } /* ---------- Animated counters ---------- */ function initCounters() { var nums = document.querySelectorAll('.stat-num[data-count]'); if (!nums.length || !('IntersectionObserver' in window)) { nums.forEach(function (n) { n.textContent = n.getAttribute('data-count'); }); return; } var io = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (!e.isIntersecting) return; var el = e.target; var target = parseInt(el.getAttribute('data-count'), 10); if (reduceMotion) { el.textContent = target; io.unobserve(el); return; } var start = null, dur = 1600; function step(ts) { if (!start) start = ts; var p = Math.min((ts - start) / dur, 1); var eased = 1 - Math.pow(1 - p, 3); el.textContent = Math.floor(eased * target); if (p < 1) requestAnimationFrame(step); else el.textContent = target; } requestAnimationFrame(step); io.unobserve(el); }); }, { threshold: 0.5 }); nums.forEach(function (n) { io.observe(n); }); } /* ---------- Card cursor glow ---------- */ function initCardGlow() { document.querySelectorAll('.card').forEach(function (card) { card.addEventListener('mousemove', function (e) { var r = card.getBoundingClientRect(); card.style.setProperty('--mx', (e.clientX - r.left) + 'px'); card.style.setProperty('--my', (e.clientY - r.top) + 'px'); }); }); } function ready(fn) { if (document.readyState !== 'loading') fn(); else document.addEventListener('DOMContentLoaded', fn); } ready(function () { initStars(); initReveal(); initHeader(); initNav(); initCounters(); initCardGlow(); initEarthVideo(); }); /* ---------- Slow the Earth video ---------- */ function initEarthVideo() { var v = document.querySelector('.earth-video'); if (!v) return; var setRate = function () { v.playbackRate = 0.55; }; setRate(); v.addEventListener('loadedmetadata', setRate); v.addEventListener('play', setRate); } })();