1// Social Media Icons - Vanilla JS animations and behavior 2const animateSocialMediaIcons = () => { 3 const socialContainers = document.querySelectorAll('.social-media-icons'); 4 if (!socialContainers.length) return; 5 6 // Respect reduced motion 7 const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 8 9 socialContainers.forEach(container => { 10 const socialLinks = container.querySelectorAll('.social-icon-link'); 11 12 if (!prefersReduced && socialLinks.length > 0) { 13 // Set initial states for entrance animation 14 socialLinks.forEach((link, index) => { 15 link.style.opacity = '0'; 16 link.style.transform = 'translateY(20px)'; 17 link.style.transition = 'opacity 0.6s ease-out, transform 0.6s ease-out'; 18 }); 19 20 // Create Intersection Observer for scroll-triggered animation 21 const observer = new IntersectionObserver((entries) => { 22 entries.forEach(entry => { 23 if (entry.isIntersecting) { 24 const links = entry.target.querySelectorAll('.social-icon-link'); 25 26 // Animate each link with staggered delay 27 links.forEach((link, index) => { 28 setTimeout(() => { 29 link.style.opacity = '1'; 30 link.style.transform = 'translateY(0)'; 31 }, index * 100); // 100ms stagger delay 32 }); 33 34 // Unobserve after animation to prevent re-triggering 35 observer.unobserve(entry.target); 36 } 37 }); 38 }, { 39 threshold: 0.15, // Trigger when 15% of container is visible 40 rootMargin: '0px 0px -15% 0px' // Equivalent to "top 85%" trigger point 41 }); 42 43 // Observe the container 44 observer.observe(container); 45 } 46 47 // Add accessibility enhancements 48 socialLinks.forEach(link => { 49 // Add focus handling for keyboard navigation 50 link.addEventListener('focus', (e) => { 51 if (container.classList.contains('hover-enabled')) { 52 e.target.style.transform = 'translateY(-2px)'; 53 e.target.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; 54 } 55 }); 56 57 link.addEventListener('blur', (e) => { 58 if (container.classList.contains('hover-enabled')) { 59 e.target.style.transform = ''; 60 e.target.style.boxShadow = ''; 61 } 62 }); 63 64 // Ensure proper ARIA labels for screen readers 65 const img = link.querySelector('.social-icon'); 66 if (img && !link.getAttribute('aria-label')) { 67 const altText = img.getAttribute('alt') || 'Social Media Link'; 68 link.setAttribute('aria-label', altText); 69 } 70 }); 71 72 // Add ripple effect on click (optional enhancement) 73 if (container.classList.contains('hover-enabled')) { 74 socialLinks.forEach(link => { 75 link.addEventListener('click', (e) => { 76 const wrapper = link.querySelector('.social-icon-wrapper'); 77 if (!wrapper) return; 78 79 // Create ripple element 80 const ripple = document.createElement('span'); 81 ripple.classList.add('ripple-effect'); 82 83 // Position ripple at click point 84 const rect = wrapper.getBoundingClientRect(); 85 const size = Math.max(rect.width, rect.height); 86 const x = e.clientX - rect.left - size / 2; 87 const y = e.clientY - rect.top - size / 2; 88 89 ripple.style.cssText = ` 90 position: absolute; 91 width: ${size}px; 92 height: ${size}px; 93 left: ${x}px; 94 top: ${y}px; 95 background: rgba(255, 255, 255, 0.3); 96 border-radius: 50%; 97 transform: scale(0); 98 animation: ripple-animation 0.6s linear; 99 pointer-events: none; 100 z-index: 1; 101 `; 102 103 // Add CSS animation if not already defined 104 if (!document.querySelector('#ripple-animation-css')) { 105 const style = document.createElement('style'); 106 style.id = 'ripple-animation-css'; 107 style.textContent = ` 108 @keyframes ripple-animation { 109 to { 110 transform: scale(2); 111 opacity: 0; 112 } 113 } 114 `; 115 document.head.appendChild(style); 116 } 117 118 wrapper.style.position = 'relative'; 119 wrapper.style.overflow = 'hidden'; 120 wrapper.appendChild(ripple); 121 122 // Remove ripple after animation 123 setTimeout(() => { 124 if (ripple.parentNode) { 125 ripple.parentNode.removeChild(ripple); 126 } 127 }, 600); 128 }); 129 }); 130 } 131 }); 132}; 133 134// Initialize on document ready 135jQuery(document).ready(() => { 136 animateSocialMediaIcons(); 137});
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.