PageSourceSearch

https://www.edinburghdirectaid.org/script-test.js

js edinburghdirectaid.org collected 2026-10-01 23:53:23 UTC 16,028 bytes, 428 lines download raw bytes

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.