1/* 2 * scroll-effects.js â Parallax & Scroll Animation Controller 3 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4 * Handles: 5 * 1. Parallax background (moves slower as you scroll) 6 * 2. Fade-in animations when sections enter viewport 7 * 3. Smooth momentum scrolling feel 8 */ 9 10(function () { 11 12 // Check if user prefers reduced motion 13 const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 14 if (prefersReducedMotion) return; // Exit if user prefers no animations 15 16 // ââ 1. PARALLAX BACKGROUND âââââââââââââââââââââââââââââââââ 17 const background = document.querySelector('body::before') || document.body; 18 let ticking = false; 19 20 function updateParallax() { 21 const scrolled = window.pageYOffset; 22 const parallaxSpeed = 0.5; // Background moves at 50% speed 23 24 // Apply parallax to the body's ::before pseudo-element via CSS variable 25 document.body.style.setProperty('--parallax-offset', scrolled * parallaxSpeed + 'px'); 26 27 ticking = false; 28 } 29 30 window.addEventListener('scroll', () => { 31 if (!ticking) { 32 window.requestAnimationFrame(updateParallax); 33 ticking = true; 34 } 35 }); 36 37 // Update parallax position via transform on body 38 function applyParallax() { 39 const scrolled = window.pageYOffset; 40 const parallaxSpeed = 0.3; // Slower = more dramatic 41 document.body.style.backgroundPositionY = -(scrolled * parallaxSpeed) + 'px'; 42 } 43 44 window.addEventListener('scroll', () => { 45 if (!ticking) { 46 window.requestAnimationFrame(applyParallax); 47 ticking = true; 48 } 49 }); 50 51 // ââ 2. SCROLL FADE-IN ANIMATIONS âââââââââââââââââââââââââââ 52 // Use Intersection Observer to detect when elements enter viewport 53 const observerOptions = { 54 root: null, // viewport 55 threshold: 0.1, // trigger when 10% of element is visible 56 rootMargin: '0px 0px -100px 0px' // trigger slightly before element enters 57 }; 58 59 const observer = new IntersectionObserver((entries) => { 60 entries.forEach(entry => { 61 if (entry.isIntersecting) { 62 entry.target.classList.add('in-view'); 63 // Optionally unobserve after animation to save resources 64 // observer.unobserve(entry.target); 65 } 66 }); 67 }, observerOptions); 68 69 // Observe all sections and header 70 const elementsToAnimate = document.querySelectorAll('section, header'); 71 elementsToAnimate.forEach(el => observer.observe(el)); 72 73 // ââ 3. SMOOTH MOMENTUM SCROLLING âââââââââââââââââââââââââââ 74 // Add CSS property for smooth scrolling 75 // (Already handled in CSS via scroll-behavior: smooth) 76 77 // Optional: Add custom easing for anchor links 78 document.querySelectorAll('a[href^="#"]').forEach(anchor => { 79 anchor.addEventListener('click', function (e) { 80 const targetId = this.getAttribute('href'); 81 if (targetId === '#') return; 82 83 const targetElement = document.querySelector(targetId); 84 if (targetElement) { 85 e.preventDefault(); 86 targetElement.scrollIntoView({ 87 behavior: 'smooth', 88 block: 'start' 89 }); 90 } 91 }); 92 }); 93 94 // ââ Initial Load: Show hero immediately ââââââââââââââââââââ 95 // Header should appear immediately without waiting for scroll 96 const header = document.querySelector('header'); 97 if (header) { 98 setTimeout(() => { 99 header.classList.add('in-view'); 100 }, 100); 101 } 102 103})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.