1 /* Existing JavaScript remains unchanged */ 2 document.addEventListener('DOMContentLoaded', () => { 3 // Select all buttons with the class 'btn-running' 4 const buttons = document.querySelectorAll('.btn-running'); 5 const startAngle = 90; // Initial angle 6 let activeButton = null; // Track the currently hovered button 7 let angle = startAngle; // Current angle 8 let animationFrame; 9 10 const updateAngle = () => { 11 if (activeButton) { 12 angle = (angle + 2) % 360; // Increment angle and wrap at 360 13 activeButton.style.setProperty('--angle', `${angle}deg`); 14 animationFrame = requestAnimationFrame(updateAngle); // Schedule next frame 15 } 16 }; 17 18 const onMouseEnter = (event) => { 19 activeButton = event.currentTarget; // Set the active button 20 angle = startAngle; // Reset angle to start value 21 updateAngle(); // Start animation 22 }; 23 24 const onMouseLeave = (event) => { 25 activeButton = null; // Clear the active button 26 cancelAnimationFrame(animationFrame); // Stop animation 27 event.currentTarget.style.setProperty('--angle', `${startAngle}deg`); // Reset angle in CSS 28 }; 29 30 // Attach event listeners to each button 31 buttons.forEach((button) => { 32 button.addEventListener('mouseenter', onMouseEnter); 33 button.addEventListener('mouseleave', onMouseLeave); 34 }); 35 }); 36 37 // Lenis Smooth Scrolling 38 const lenis = new Lenis({ 39 lerp: 0.1, 40 wheelMultiplier: 1, 41 }); 42 function raf(time) { 43 lenis.raf(time); 44 requestAnimationFrame(raf); 45 } 46 requestAnimationFrame(raf); 47 48 // Intersection Observer for panels 49 document.addEventListener('DOMContentLoaded', () => { 50 const panels = document.querySelectorAll('.panel'); 51 const observer = new IntersectionObserver((entries) => { 52 entries.forEach((entry) => { 53 if (entry.isIntersecting) { 54 entry.target.classList.add('active'); 55 } else { 56 entry.target.classList.remove('active'); 57 } 58 }); 59 }, { 60 threshold: 0.1 61 }); 62 63 panels.forEach((panel) => observer.observe(panel)); 64 }); 65 66 // Slider logic (GSAP) ... 67 document.addEventListener('DOMContentLoaded', () => { 68 const sliderContainers = document.querySelectorAll('.slider-container'); // Each slider container 69 70 sliderContainers.forEach((sliderContainer) => { 71 const slides = sliderContainer.querySelectorAll('.slide'); 72 const bullets = sliderContainer.querySelectorAll('.bullet'); 73 const nextButton = sliderContainer.querySelector('.nextButton'); 74 const prevButton = sliderContainer.querySelector('.prevButton'); 75 let currentIndex = 0; 76 let isTransitioning = false; 77 78 // Initial setup 79 setupInitialVisibility(slides); 80 updateBullets(); 81 82 function setupInitialVisibility(slides) { 83 gsap.set(slides[0], { visibility: 'visible', zIndex: 1 }); 84 slides.forEach((slide, index) => { 85 if (index !== 0) { 86 gsap.set(slide, { visibility: 'hidden', zIndex: -1 }); 87 } 88 }); 89 } 90 91 function maskAndScale(element, options = {}, direction = 'next') { 92 const { duration = 0.8, scaleStart = 1.2, scaleEnd = 1 } = { 93 ...{ duration: 0.8, scaleStart: 1.2, scaleEnd: 1 }, 94 ...options 95 }; 96 97 const clipStart = direction === 'next' 98 ? 'polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%)' 99 : 'polygon(100% 0, 100% 0, 100% 100%, 100% 100%)'; 100 101 const clipEnd = 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)'; 102 103 gsap.fromTo( 104 element, 105 { clipPath: clipStart, scale: scaleStart }, 106 { clipPath: clipEnd, scale: scaleEnd, duration, ease: 'power4.out' } 107 ); 108 } 109 110 function showSlide(index, direction) { 111 const currentSlide = slides[currentIndex]; 112 const nextSlide = slides[index]; 113 if (currentSlide === nextSlide) return; 114 115 // Transition out current 116 gsap.set(currentSlide, { zIndex: 0 });
117 gsap.delayedCall(0.8, () => { 118 gsap.set(currentSlide, { visibility: 'hidden', zIndex: -1 }); 119 }); 120 121 // Prepare next 122 gsap.set(nextSlide, { visibility: 'visible', zIndex: 1 }); 123 const largeImage = nextSlide.querySelector('.masked-image-wrapper-one img'); 124 const smallImage = nextSlide.querySelector('.masked-image-wrapper-two img'); 125 126 // Animate 127 maskAndScale(largeImage, {}, direction); 128 if (smallImage) { 129 maskAndScale(smallImage, {}, direction); 130 } 131 132 currentIndex = index; 133 } 134 135 function changeSlide(index, direction) { 136 if (isTransitioning) return; 137 isTransitioning = true; 138 139 showSlide(index, direction); 140 updateBullets(); 141 142 setTimeout(() => { 143 isTransitioning = false; 144 }, 1000); 145 } 146 147 function nextSlide() { 148 const nextIndex = (currentIndex + 1) % slides.length; 149 changeSlide(nextIndex, 'next'); 150 } 151 152 function prevSlide() { 153 const prevIndex = (currentIndex - 1 + slides.length) % slides.length; 154 changeSlide(prevIndex, 'prev'); 155 } 156 157 function updateBullets() { 158 if (!bullets.length) return; 159 bullets.forEach((bullet, index) => { 160 bullet.classList.toggle('active', index === currentIndex); 161 }); 162 } 163 164 // Button event listeners 165 if (nextButton) nextButton.addEventListener('click', nextSlide); 166 if (prevButton) prevButton.addEventListener('click', prevSlide); 167 168 // Bullet event listeners 169 bullets.forEach((bullet) => { 170 bullet.addEventListener('click', () => { 171 const index = parseInt(bullet.getAttribute('data-index'), 10); 172 const direction = index > currentIndex ? 'next' : 'prev'; 173 changeSlide(index, direction); 174 }); 175 }); 176 }); 177 });
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.