1// yaml.org Custom JavaScript 2 3// MathJax configuration 4window.MathJax = { 5 tex: { 6 inlineMath: [['$', '$'], ['\\(', '\\)']], 7 displayMath: [['$$', '$$'], ['\\[', '\\]']], 8 processEscapes: true, 9 processEnvironments: true 10 }, 11 options: { 12 ignoreHtmlClass: 'tex2jax_ignore', 13 processHtmlClass: 'tex2jax_process' 14 } 15}; 16 17// Custom functionality for yaml.org 18document.addEventListener('DOMContentLoaded', function() { 19 20 // Add smooth scrolling to anchor links 21 document.querySelectorAll('a[href^="#"]').forEach(anchor => { 22 anchor.addEventListener('click', function (e) { 23 e.preventDefault(); 24 const target = document.querySelector(this.getAttribute('href')); 25 if (target) { 26 target.scrollIntoView({ 27 behavior: 'smooth', 28 block: 'start' 29 }); 30 } 31 }); 32 }); 33 34 // Add sponsor card hover effects 35 document.querySelectorAll('.sponsor-card').forEach(card => { 36 card.addEventListener('mouseenter', function() { 37 this.style.transform = 'translateY(-4px)'; 38 }); 39 40 card.addEventListener('mouseleave', function() { 41 this.style.transform = 'translateY(0)'; 42 }); 43 }); 44 45 // Add stats counter animation 46 function animateCounters() { 47 document.querySelectorAll('.stat-number').forEach(counter => { 48 const target = parseInt(counter.textContent); 49 const duration = 2000; 50 const step = target / (duration / 16); 51 let current = 0; 52 53 const timer = setInterval(() => { 54 current += step; 55 if (current >= target) { 56 current = target; 57 clearInterval(timer); 58 } 59 counter.textContent = Math.floor(current).toLocaleString(); 60 }, 16); 61 }); 62 } 63 64 // Trigger counter animation when stats come into view 65 const observer = new IntersectionObserver((entries) => { 66 entries.forEach(entry => { 67 if (entry.isIntersecting) { 68 animateCounters(); 69 observer.unobserve(entry.target); 70 } 71 }); 72 }); 73 74 const statsSection = document.querySelector('.stats-grid'); 75 if (statsSection) { 76 observer.observe(statsSection); 77 } 78 79 // Add mobile menu improvements 80 const mobileMenuToggle = document.querySelector('.md-header__button'); 81 if (mobileMenuToggle) { 82 mobileMenuToggle.addEventListener('click', function() { 83 document.body.classList.toggle('mobile-menu-open'); 84 }); 85 } 86 87 // Add search highlighting 88 const searchInput = document.querySelector('.md-search__input'); 89 if (searchInput) { 90 searchInput.addEventListener('input', function() { 91 const query = this.value.toLowerCase(); 92 if (query.length > 2) { 93 highlightSearchResults(query); 94 } else { 95 clearSearchHighlights(); 96 } 97 }); 98 } 99 100 // Search result highlighting function 101 function highlightSearchResults(query) { 102 clearSearchHighlights(); 103 104 document.querySelectorAll('main').forEach(main => { 105 const walker = document.createTreeWalker( 106 main, 107 NodeFilter.SHOW_TEXT, 108 null, 109 false 110 ); 111 112 let node; 113 while (node = walker.nextNode()) { 114 const text = node.textContent; 115 if (text.toLowerCase().includes(query)) { 116 const span = document.createElement('span'); 117 span.className = 'search-highlight'; 118 span.style.backgroundColor = 'rgba(255, 193, 7, 0.3)'; 119 span.style.padding = '2px 4px'; 120 span.style.borderRadius = '2px'; 121 122 const regex = new RegExp(`(${query})`, 'gi'); 123 const highlightedText = text.replace(regex, '<span class="search-highlight">$1</span>'); 124 125 const wrapper = document.createElement('span'); 126 wrapper.innerHTML = highlightedText; 127 node.parentNode.replaceChild(wrapper, node); 128 } 129 } 130 }); 131 } 132 133 // Clear search highlights 134 function clearSearchHighlights() { 135 document.querySelectorAll('.search-highlight').forEach(highlight => { 136 const parent = highlight.parentNode; 137 parent.replaceChild(document.createTextNode(highlight.textContent), highlight); 138 parent.normalize(); 139 }); 140 } 141 142 // Add sponsor tier selection 143 document.querySelectorAll('.sponsor-tier-selector').forEach(selector => { 144 selector.addEventListener('change', function() { 145 const selectedTier = this.value; 146 updateSponsorDisplay(selectedTier); 147 }); 148 }); 149 150 // Update sponsor display based on selection 151 function updateSponsorDisplay(tier) { 152 document.querySelectorAll('.sponsor-card').forEach(card => { 153 if (tier === 'all' || card.classList.contains(tier)) { 154 card.style.display = 'block'; 155 } else { 156 card.style.display = 'none'; 157 } 158 }); 159 } 160 161 // Add smooth page transitions 162 document.querySelectorAll('a[href^="/"]').forEach(link => { 163 link.addEventListener('click', function(e) { 164 if (this.hostname === window.location.hostname) { 165 e.preventDefault(); 166 const targetUrl = this.href; 167 168 // Add loading state 169 document.body.classList.add('page-loading'); 170 171 // Simulate page transition 172 setTimeout(() => { 173 window.location.href = targetUrl; 174 }, 300); 175 } 176 }); 177 }); 178 179 // Add back to top button 180 const backToTopButton = document.createElement('button'); 181 backToTopButton.innerHTML = 'â'; 182 backToTopButton.className = 'back-to-top'; 183 backToTopButton.style.cssText = `
184 position: fixed; 185 bottom: 20px; 186 right: 20px; 187 width: 50px; 188 height: 50px; 189 border-radius: 50%; 190 background: var(--yaml-primary); 191 color: white; 192 border: none; 193 cursor: pointer; 194 font-size: 20px; 195 opacity: 0; 196 transition: opacity 0.3s ease; 197 z-index: 1000; 198 `; 199 200 document.body.appendChild(backToTopButton); 201 202 // Show/hide back to top button 203 window.addEventListener('scroll', function() { 204 if (window.pageYOffset > 300) { 205 backToTopButton.style.opacity = '1'; 206 } else { 207 backToTopButton.style.opacity = '0'; 208 } 209 }); 210 211 // Back to top functionality 212 backToTopButton.addEventListener('click', function() { 213 window.scrollTo({ 214 top: 0, 215 behavior: 'smooth' 216 }); 217 }); 218 219 // Add sponsor contact form handling 220 const sponsorForms = document.querySelectorAll('.sponsor-contact-form'); 221 sponsorForms.forEach(form => { 222 form.addEventListener('submit', function(e) { 223 e.preventDefault(); 224 225 // Get form data 226 const formData = new FormData(this); 227 const data = Object.fromEntries(formData); 228 229 // Show success message 230 showNotification('Thank you for your interest! We\'ll be in touch soon.', 'success'); 231 232 // Reset form 233 this.reset(); 234 }); 235 }); 236 237 // Notification system 238 function showNotification(message, type = 'info') { 239 const notification = document.createElement('div'); 240 notification.className = `notification notification-${type}`; 241 notification.textContent = message; 242 notification.style.cssText = ` 243 position: fixed; 244 top: 20px; 245 right: 20px; 246 padding: 1rem 1.5rem; 247 border-radius: 4px; 248 color: white; 249 font-weight: 500; 250 z-index: 1001; 251 transform: translateX(100%); 252 transition: transform 0.3s ease; 253 `; 254 255 // Set background color based on type 256 switch (type) { 257 case 'success': 258 notification.style.background = 'var(--yaml-secondary)'; 259 break; 260 case 'error': 261 notification.style.background = '#f44336'; 262 break; 263 default: 264 notification.style.background = 'var(--yaml-primary)'; 265 } 266 267 document.body.appendChild(notification); 268 269 // Animate in 270 setTimeout(() => { 271 notification.style.transform = 'translateX(0)'; 272 }, 100); 273 274 // Auto-remove after 5 seconds 275 setTimeout(() => { 276 notification.style.transform = 'translateX(100%)'; 277 setTimeout(() => { 278 document.body.removeChild(notification); 279 }, 300); 280 }, 5000); 281 } 282 283 console.log('yaml.org JavaScript loaded successfully!'); 284});
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.