// Initialize Lucide Icons lucide.createIcons(); // Register GSAP plugins gsap.registerPlugin(ScrollTrigger); // ========================================== // CUSTOM CURSOR // ========================================== const cursorDot = document.querySelector('.cursor-dot'); const cursorOutline = document.querySelector('.cursor-outline'); if (window.matchMedia("(min-width: 768px)").matches) { window.addEventListener('mousemove', (e) => { const posX = e.clientX; const posY = e.clientY; // Dot follows immediately cursorDot.style.left = `${posX}px`; cursorDot.style.top = `${posY}px`; // Outline follows with slight delay (using GSAP for smoothness) gsap.to(cursorOutline, { x: posX, y: posY, duration: 0.15, ease: "power2.out" }); }); // Hover effects for interactive elements const interactiveElements = document.querySelectorAll('a, button, .tilt-card, input, textarea'); interactiveElements.forEach(el => { el.addEventListener('mouseenter', () => { document.body.classList.add('hovering'); gsap.to(cursorOutline, { scale: 1.5, duration: 0.3 }); }); el.addEventListener('mouseleave', () => { document.body.classList.remove('hovering'); gsap.to(cursorOutline, { scale: 1, duration: 0.3 }); }); }); } // ========================================== // REVERSE COMPASS SCROLLBAR // ========================================== // The compass needle moves UP when scrolling DOWN (reverse behavior) // and rotates based on scroll velocity const compassNeedle = document.getElementById('compassNeedle'); let lastScrollY = window.scrollY; let scrollVelocity = 0; function updateCompassScroll() { const scrollHeight = document.documentElement.scrollHeight - window.innerHeight; const scrollTop = window.scrollY; const scrollPercent = scrollTop / scrollHeight; // Reverse behavior: needle moves up as page scrolls down const containerHeight = 200; // height of scroll container const needlePosition = containerHeight - (scrollPercent * containerHeight); if (compassNeedle) { compassNeedle.style.top = `${needlePosition}px`; // Calculate velocity for rotation scrollVelocity = scrollTop - lastScrollY; const rotation = scrollVelocity * 2; // amplify rotation gsap.to(compassNeedle, { rotation: rotation, duration: 0.1 }); } lastScrollY = scrollTop; } // Throttled scroll listener let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { window.requestAnimationFrame(() => { updateCompassScroll(); ticking = false; }); ticking = true; } }); // ========================================== // D3 COMPASS VISUALIZATION (Hero Background) // ========================================== const initD3Compass = () => { const width = window.innerWidth; const height = window.innerHeight; const svg = d3.select("#compass-viz") .append("svg") .attr("width", width) .attr("height", height) .attr("viewBox", `0 0 ${width} ${height}`); const centerX = width / 2; const centerY = height / 2; // Create concentric circles const circles = [100, 200, 300, 400]; circles.forEach(radius => { svg.append("circle") .attr("cx", centerX) .attr("cy", centerY) .attr("r", radius) .attr("class", "compass-circle"); }); // Create compass rose lines for (let i = 0; i < 360; i += 30) { const angle = (i * Math.PI) / 180; const x2 = centerX + Math.cos(angle) * 400; const y2 = centerY + Math.sin(angle) * 400; svg.append("line") .attr("x1", centerX) .attr("y1", centerY) .attr("x2", x2) .attr("y2", y2) .attr("class", "compass-rose-line"); } // Animate on scroll gsap.to(".compass-circle", { scrollTrigger: { trigger: "body", start: "top top", end: "bottom bottom", scrub: 1 }, attr: { r: (i, target) => 100 + (i * 100) + 50 }, opacity: 0 }); gsap.to(".compass-rose-line", { scrollTrigger: { trigger: "body", start: "top top", end: "bottom bottom", scrub: 1 }, rotation: 45, transformOrigin: "center" }); }; if (window.matchMedia("(min-width: 1024px)").matches) { initD3Compass(); } // ========================================== // TEXT REVEAL ANIMATIONS // ========================================== const lineInners = document.querySelectorAll('.line-inner'); lineInners.forEach((line, index) => { gsap.to(line, { y: 0, duration: 1, ease: "power4.out", delay: index * 0.1, scrollTrigger: { trigger: line, start: "top 85%", toggleActions: "play none none none" } }); }); // Hero subtext fade in gsap.to("#heroSubtext", { opacity: 1, y: 0, duration: 1, delay: 0.5, ease: "power2.out" }); // ========================================== // PARALLAX BACKGROUND GRID // ========================================== const gridBg = document.getElementById('gridBg'); if (gridBg) { gsap.to(gridBg, { scrollTrigger: { trigger: "body", start: "top top", end: "bottom bottom", scrub: 0.5 }, backgroundPosition: "50% 100%" }); } // ========================================== // 3D TILT EFFECTS ON CARDS // ========================================== const tiltCards = document.querySelectorAll('.tilt-card'); tiltCards.forEach(card => { card.addEventListener('mousemove', (e) => { const rect = card.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const centerX = rect.width / 2; const centerY = rect.height / 2; const rotateX = (y - centerY) / centerY * -10; // Max 10deg rotation const rotateY = (x - centerX) / centerX * 10; gsap.to(card, { rotationX: rotateX, rotationY: rotateY, transformPerspective: 1000, duration: 0.3, ease: "power2.out" }); }); card.addEventListener('mouseleave', () => { gsap.to(card, { rotationX: 0, rotationY: 0, duration: 0.5, ease: "power2.out" }); }); }); // ========================================== // NAVIGATION SHOW/HIDE ON SCROLL // ========================================== const nav = document.getElementById('mainNav'); let lastScroll = 0; window.addEventListener('scroll', () => { const currentScroll = window.pageYOffset; if (currentScroll > 100) { if (currentScroll > lastScroll) { // Scrolling down nav.classList.remove('nav-visible'); nav.classList.add('nav-hidden'); } else { // Scrolling up nav.classList.remove('nav-hidden'); nav.classList.add('nav-visible'); } } else { nav.classList.add('nav-hidden'); nav.classList.remove('nav-visible'); } lastScroll = currentScroll; }); // ========================================== // FAQ ACCORDION // ========================================== function toggleFaq(element) { const content = element.querySelector('.faq-content'); const isActive = element.classList.contains('active'); // Close all others document.querySelectorAll('.faq-item').forEach(item => { item.classList.remove('active'); item.querySelector('.faq-content').classList.remove('active'); }); // Open clicked if it wasn't active if (!isActive) { element.classList.add('active'); content.classList.add('active'); } } // ========================================== // SMOOTH SCROLL FOR ANCHOR LINKS // ========================================== document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // ========================================== // MAGNETIC BUTTON EFFECT // ========================================== const magneticBtns = document.querySelectorAll('.magnetic-btn'); magneticBtns.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; gsap.to(btn, { x: x * 0.3, y: y * 0.3, duration: 0.3, ease: "power2.out" }); }); btn.addEventListener('mouseleave', () => { gsap.to(btn, { x: 0, y: 0, duration: 0.5, ease: "elastic.out(1, 0.3)" }); }); }); // ========================================== // IMAGE PARALLAX EFFECT // ========================================== const parallaxImages = document.querySelectorAll('.parallax-img'); parallaxImages.forEach(img => { const speed = img.getAttribute('data-speed') || 0.5; gsap.to(img, { scrollTrigger: { trigger: img.parentElement, start: "top bottom", end: "bottom top", scrub: true }, y: -50 * speed, scale: 1.1 }); });