// Developer: Saksham Pathak (github.com/parthmax2) // Shared UI behaviors: nav scroll state, cursor glow, magnetic buttons, // staggered scroll-reveal, animated stat counters. function initNavScroll() { const navbar = document.querySelector(".navbar"); if (!navbar) return; const update = () => navbar.classList.toggle("navbar-scrolled", window.scrollY > 10); update(); window.addEventListener("scroll", update, { passive: true }); } function initCursorGlow() { if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) return; const glow = document.createElement("div"); glow.className = "cursor-glow"; document.body.appendChild(glow); window.addEventListener("mousemove", (e) => { glow.style.setProperty("--x", `${e.clientX}px`); glow.style.setProperty("--y", `${e.clientY}px`); }); } function initMagneticButtons() { const strength = 0.25; document.querySelectorAll(".btn-primary, .btn-secondary").forEach((btn) => { btn.addEventListener("mousemove", (e) => { const rect = btn.getBoundingClientRect(); const x = e.clientX - rect.left - rect.width / 2; const y = e.clientY - rect.top - rect.height / 2; btn.style.transform = `translate(${x * strength}px, ${y * strength}px)`; }); btn.addEventListener("mouseleave", () => { btn.style.transform = ""; }); }); } // Assigns a stagger index (--i) to each .reveal within a group, then reveals // elements as they cross the viewport. function initStaggerReveal() { document.querySelectorAll(".reveal-group").forEach((group) => { Array.from(group.children).forEach((child, i) => { if (child.classList.contains("reveal")) child.style.setProperty("--i", i); }); }); const items = document.querySelectorAll(".reveal:not(.reveal-visible)"); if (!("IntersectionObserver" in window)) { items.forEach((el) => el.classList.add("reveal-visible")); return; } const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("reveal-visible"); observer.unobserve(entry.target); } }); }, { threshold: 0.15 }); items.forEach((el) => observer.observe(el)); } function initCounters() { const counters = document.querySelectorAll("[data-count-to]"); if (counters.length === 0) return; const animate = (el) => { const target = parseFloat(el.dataset.countTo); const decimals = el.dataset.countTo.includes(".") ? el.dataset.countTo.split(".")[1].length : 0; const suffix = el.dataset.countSuffix || ""; const duration = 1400; const start = performance.now(); function tick(now) { const progress = Math.min((now - start) / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); el.textContent = (target * eased).toFixed(decimals) + suffix; if (progress < 1) requestAnimationFrame(tick); } requestAnimationFrame(tick); }; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { animate(entry.target); observer.unobserve(entry.target); } }); }, { threshold: 0.5 }); counters.forEach((el) => observer.observe(el)); } initNavScroll(); initCursorGlow(); initMagneticButtons(); initStaggerReveal(); initCounters();