1// Mobile Navigation Toggle (event delegation so it works with dynamically loaded header) 2document.addEventListener('click', function(e) { 3 if (e.target.closest('.hamburger')) { 4 var hamburger = document.querySelector('.hamburger'); 5 var navMenu = document.querySelector('.nav-menu'); 6 if (hamburger && navMenu) { 7 hamburger.classList.toggle('active'); 8 navMenu.classList.toggle('active'); 9 if (!navMenu.classList.contains('active')) { 10 navMenu.classList.remove('submenu-open'); 11 document.querySelectorAll('.nav-item-dropdown').forEach(function(item) { 12 item.classList.remove('active'); 13 }); 14 } 15 document.body.style.overflow = navMenu.classList.contains('active') ? 'hidden' : ''; 16 } 17 } 18}); 19 20// Dropdown and nav init (runs on DOMContentLoaded and when header is loaded via includes) 21function initEDANav() { 22 var dropdownItems = document.querySelectorAll('.nav-item-dropdown'); 23 dropdownItems.forEach(function(item) { 24 var navLink = item.querySelector('.nav-link'); 25 if (!navLink || navLink._edaDropdownInit) return; 26 navLink._edaDropdownInit = true; 27 navLink.addEventListener('click', function(e) { 28 e.preventDefault(); 29 e.stopPropagation(); 30 var isActive = item.classList.contains('active'); 31 dropdownItems.forEach(function(other) { if (other !== item) other.classList.remove('active'); }); 32 if (isActive) { 33 item.classList.remove('active'); 34 if (window.innerWidth <= 768) { 35 var m = document.querySelector('.nav-menu'); 36 if (m) m.classList.remove('submenu-open'); 37 } 38 } else { 39 item.classList.add('active'); 40 if (window.innerWidth <= 768) { 41 var m2 = document.querySelector('.nav-menu'); 42 if (m2) m2.classList.add('submenu-open'); 43 } 44 } 45 }); 46 }); 47 document.querySelectorAll('.nav-link').forEach(function(link) { 48 if (link.closest('.nav-item-dropdown')) return; 49 if (link._edaNavCloseInit) return; 50 link._edaNavCloseInit = true; 51 link.addEventListener('click', function() { 52 if (window.innerWidth <= 768) { 53 var h = document.querySelector('.hamburger'); var m = document.querySelector('.nav-menu'); 54 if (h) h.classList.remove('active'); 55 if (m) { 56 m.classList.remove('active'); 57 m.classList.remove('submenu-open'); 58 } 59 document.body.style.overflow = ''; 60 } 61 }); 62 }); 63 document.querySelectorAll('.dropdown-link').forEach(function(link) { 64 if (link._edaDropLinkInit) return; 65 link._edaDropLinkInit = true; 66 link.addEventListener('click', function() { 67 if (window.innerWidth <= 768) { 68 var d = this.closest('.nav-item-dropdown'); 69 if (d) d.classList.remove('active'); 70 var h = document.querySelector('.hamburger'); var m = document.querySelector('.nav-menu'); 71 if (h) h.classList.remove('active'); 72 if (m) { 73 m.classList.remove('active'); 74 m.classList.remove('submenu-open'); 75 } 76 document.body.style.overflow = ''; 77 } 78 }); 79 }); 80 document.querySelectorAll('.dropdown-back').forEach(function(btn) { 81 if (btn._edaDropBackInit) return; 82 btn._edaDropBackInit = true; 83 btn.addEventListener('click', function() { 84 document.querySelectorAll('.nav-item-dropdown').forEach(function(item) { 85 item.classList.remove('active'); 86 }); 87 var m = document.querySelector('.nav-menu'); 88 if (m) m.classList.remove('submenu-open'); 89 }); 90 }); 91} 92 93document.addEventListener('DOMContentLoaded', initEDANav); 94document.addEventListener('eda-includes-loaded', initEDANav); 95 96// Close dropdowns and mobile menu when clicking outside (works with dynamic header) 97document.addEventListener('click', function(e) { 98 if (!e.target.closest('.nav-item-dropdown')) { 99 document.querySelectorAll('.nav-item-dropdown').forEach(function(item) { item.classList.remove('active'); }); 100 } 101 if (!e.target.closest('.hamburger') && !e.target.closest('.nav-menu')) { 102 var h = document.querySelector('.hamburger'); var m = document.querySelector('.nav-menu'); 103 if (h) h.classList.remove('active'); 104 if (m) { 105 m.classList.remove('active'); 106 m.classList.remove('submenu-open'); 107 } 108 document.body.style.overflow = ''; 109 } 110}); 111 112// Smooth scrolling for donate button 113function scrollToDonate() { 114 const donateSection = document.getElementById('donate'); 115 if (donateSection) { 116 donateSection.scrollIntoView({ 117 behavior: 'smooth', 118 block: 'start' 119 }); 120 } 121} 122 123// Donate option selection 124document.addEventListener('DOMContentLoaded', function() { 125 const donateOptions = document.querySelectorAll('.donate-option'); 126 127 donateOptions.forEach(option => { 128 option.addEventListener('click', function() { 129 // Remove active class from all options 130 donateOptions.forEach(opt => opt.classList.remove('active')); 131 // Add active class to clicked option 132 this.classList.add('active'); 133 }); 134 }); 135}); 136 137// Newsletter form submission 138document.addEventListener('DOMContentLoaded', function() { 139 const newsletterForm = document.querySelector('.newsletter-form'); 140 141 if (newsletterForm) { 142 newsletterForm.addEventListener('submit', function(e) { 143 e.preventDefault(); 144 145 const email = this.querySelector('input[type="email"]').value; 146 147 if (email) { 148 // Show success message 149 const successMessage = document.createElement('div'); 150 successMessage.textContent = 'Thank you for subscribing!'; 151 successMessage.style.color = '#d32f2f'; 152 successMessage.style.marginTop = '1rem'; 153 successMessage.style.fontWeight = '600'; 154 155 this.appendChild(successMessage); 156 157 // Clear the form 158 this.querySelector('input[type="email"]').value = ''; 159 160 // Remove success message after 3 seconds 161 setTimeout(() => { 162 successMessage.remove(); 163 }, 3000); 164 } 165 }); 166 } 167}); 168 169// Animate elements on scroll
170function animateOnScroll() { 171 const elements = document.querySelectorAll('.impact-card, .update-card, .value-item, .approach-card'); 172 173 elements.forEach(element => { 174 const elementTop = element.getBoundingClientRect().top; 175 const elementVisible = 150; 176 177 if (elementTop < window.innerHeight - elementVisible) { 178 element.style.opacity = '1'; 179 element.style.transform = 'translateY(0)'; 180 } 181 }); 182} 183 184// Initialize animation styles 185document.addEventListener('DOMContentLoaded', function() { 186 const elements = document.querySelectorAll('.impact-card, .update-card, .value-item, .approach-card'); 187 188 elements.forEach(element => { 189 element.style.opacity = '0'; 190 element.style.transform = 'translateY(20px)'; 191 element.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; 192 }); 193 194 // Trigger initial animation 195 animateOnScroll(); 196}); 197 198// Add scroll event listener 199window.addEventListener('scroll', animateOnScroll); 200 201// Donate button functionality - only for buttons that don't have href attributes 202document.addEventListener('DOMContentLoaded', function() { 203 const donateButtons = document.querySelectorAll('.donate-btn-large'); 204 205 donateButtons.forEach(button => { 206 // Only add popup functionality if the button doesn't have an href (is not a link) 207 if (!button.hasAttribute('href')) { 208 button.addEventListener('click', function() { 209 // You can integrate with a payment processor here 210 alert('Thank you for your interest in donating! Please contact us at [email protected] to complete your donation.'); 211 }); 212 } 213 }); 214}); 215 216// Custom amount input functionality 217document.addEventListener('DOMContentLoaded', function() { 218 const customAmountBtn = document.querySelector('.donate-option.custom'); 219 220 if (customAmountBtn) { 221 customAmountBtn.addEventListener('click', function() { 222 const amount = prompt('Please enter your donation amount (£):'); 223 if (amount && !isNaN(amount) && amount > 0) { 224 this.textContent = `£${amount}`; 225 this.classList.add('active'); 226 } 227 }); 228 } 229}); 230 231// Add loading animation for page transitions 232document.addEventListener('DOMContentLoaded', function() { 233 // Remove any loading overlay if it exists 234 const loadingOverlay = document.querySelector('.loading-overlay'); 235 if (loadingOverlay) { 236 loadingOverlay.style.opacity = '0'; 237 setTimeout(() => { 238 loadingOverlay.remove(); 239 }, 500); 240 } 241}); 242 243// Add hover effects for cards 244document.addEventListener('DOMContentLoaded', function() { 245 const cards = document.querySelectorAll('.impact-card, .update-card, .value-item, .approach-card'); 246 247 cards.forEach(card => { 248 card.addEventListener('mouseenter', function() { 249 this.style.transform = 'translateY(-5px) scale(1.02)'; 250 }); 251 252 card.addEventListener('mouseleave', function() { 253 this.style.transform = 'translateY(0) scale(1)'; 254 }); 255 }); 256}); 257 258// Form validation for newsletter 259document.addEventListener('DOMContentLoaded', function() { 260 const emailInput = document.querySelector('.newsletter-form input[type="email"]'); 261 262 if (emailInput) { 263 emailInput.addEventListener('blur', function() { 264 const email = this.value; 265 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 266 267 if (email && !emailRegex.test(email)) { 268 this.style.borderColor = '#d32f2f'; 269 this.style.boxShadow = '0 0 0 2px rgba(211, 47, 47, 0.2)'; 270 } else { 271 this.style.borderColor = '#e0e0e0'; 272 this.style.boxShadow = 'none'; 273 } 274 }); 275 } 276}); 277 278// Add scroll to top functionality 279function addScrollToTop() { 280 const scrollToTopBtn = document.createElement('button'); 281 scrollToTopBtn.innerHTML = 'â'; 282 scrollToTopBtn.style.cssText = ` 283 position: fixed; 284 bottom: 20px; 285 right: 20px; 286 width: 50px; 287 height: 50px; 288 border-radius: 50%; 289 background-color: #d32f2f; 290 color: white; 291 border: none; 292 cursor: pointer; 293 font-size: 20px; 294 font-weight: bold; 295 opacity: 0; 296 transition: opacity 0.3s ease; 297 z-index: 1000; 298 box-shadow: 0 4px 15px rgba(211, 47, 47, 0.3); 299 `; 300 301 document.body.appendChild(scrollToTopBtn); 302 303 // Show/hide button based on scroll position 304 window.addEventListener('scroll', () => { 305 if (window.pageYOffset > 300) { 306 scrollToTopBtn.style.opacity = '1'; 307 } else { 308 scrollToTopBtn.style.opacity = '0'; 309 } 310 }); 311 312 // Scroll to top when clicked 313 scrollToTopBtn.addEventListener('click', () => { 314 window.scrollTo({ 315 top: 0, 316 behavior: 'smooth' 317 }); 318 }); 319} 320 321// Initialize scroll to top button 322document.addEventListener('DOMContentLoaded', addScrollToTop); 323 324// Homepage â OUR WORK carousel (country tabs + horizontal scroll + arrows) 325function initProgrammesCarousel() { 326 var carousel = document.querySelector('.programmes-carousel'); 327 if (!carousel || carousel._edaCarouselInit) return; 328 var track = carousel.querySelector('.programmes-track'); 329 if (!track) return; 330 carousel._edaCarouselInit = true; 331 332 var tabs = Array.prototype.slice.call(document.querySelectorAll('.programmes-tab')); 333 var cards = Array.prototype.slice.call(track.querySelectorAll('.programme-card')); 334 var prevBtn = carousel.querySelector('.programmes-arrow--prev'); 335 var nextBtn = carousel.querySelector('.programmes-arrow--next'); 336 337 function targetScrollLeft(card) { 338 // Robust: convert the card's on-screen position into a scroll offset for the track. 339 return track.scrollLeft + (card.getBoundingClientRect().left - track.getBoundingClientRect().left); 340 } 341 342 function setActive(index) {
343 tabs.forEach(function(tab) { 344 tab.classList.toggle('is-active', parseInt(tab.getAttribute('data-index'), 10) === index); 345 }); 346 } 347 348 function currentIndex() { 349 var trackLeft = track.getBoundingClientRect().left; 350 var best = 0; 351 var bestDist = Infinity; 352 cards.forEach(function(card, i) { 353 var dist = Math.abs(card.getBoundingClientRect().left - trackLeft); 354 if (dist < bestDist) { bestDist = dist; best = i; } 355 }); 356 return best; 357 } 358 359 function updateArrows() { 360 if (!prevBtn || !nextBtn) return; 361 var atStart = track.scrollLeft <= 1; 362 var atEnd = track.scrollLeft + track.clientWidth >= track.scrollWidth - 1; 363 prevBtn.disabled = atStart; 364 nextBtn.disabled = atEnd; 365 } 366 367 function goTo(index) { 368 var clamped = Math.max(0, Math.min(cards.length - 1, index)); 369 var card = cards[clamped]; 370 if (!card) return; 371 track.scrollTo({ left: targetScrollLeft(card), behavior: 'smooth' }); 372 setActive(clamped); 373 } 374 375 function step(direction) { 376 // Scroll by roughly one card (width + gap), clamped within range. 377 var first = cards[0]; 378 var second = cards[1]; 379 var amount = first ? first.getBoundingClientRect().width : track.clientWidth; 380 if (first && second) { 381 amount = second.getBoundingClientRect().left - first.getBoundingClientRect().left; 382 } 383 track.scrollBy({ left: direction * amount, behavior: 'smooth' }); 384 } 385
386 tabs.forEach(function(tab) { 387 tab.addEventListener('click', function() { 388 goTo(parseInt(tab.getAttribute('data-index'), 10)); 389 }); 390 }); 391 392 if (prevBtn) prevBtn.addEventListener('click', function() { step(-1); }); 393 if (nextBtn) nextBtn.addEventListener('click', function() { step(1); }); 394 395 var scrollRaf; 396 track.addEventListener('scroll', function() { 397 if (scrollRaf) cancelAnimationFrame(scrollRaf); 398 scrollRaf = requestAnimationFrame(function() { 399 setActive(currentIndex()); 400 updateArrows(); 401 }); 402 }); 403 404 window.addEventListener('resize', updateArrows); 405 updateArrows(); 406} 407 408document.addEventListener('DOMContentLoaded', initProgrammesCarousel); 409 410// Tab functionality for donation guidelines 411document.addEventListener('DOMContentLoaded', function() { 412 const tabButtons = document.querySelectorAll('.tab-button'); 413 const tabPanels = document.querySelectorAll('.tab-panel'); 414 415 tabButtons.forEach(button => { 416 button.addEventListener('click', function() { 417 const targetTab = this.getAttribute('data-tab'); 418 419 // Remove active class from all buttons and panels 420 tabButtons.forEach(btn => btn.classList.remove('active')); 421 tabPanels.forEach(panel => panel.classList.remove('active')); 422 423 // Add active class to clicked button and corresponding panel 424 this.classList.add('active'); 425 document.getElementById(targetTab).classList.add('active'); 426 }); 427 }); 428});
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.