| |
| |
| |
|
|
| 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 = ""; }); |
| }); |
| } |
|
|
| |
| |
| 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(); |
|
|