




.avif)


.avif)
.avif)

.avif)
.avif)

.avif)
.avif)

.avif)
.avif)








%20(1).webp)






%20(1)%20(1)%20(1)%20(1)%20(1).webp)
.webp)















tag */ (function() { 'use strict'; // Support both formats: {current.year} and {current_year} const YEAR_PLACEHOLDERS = ['{current.year}', '{current_year}']; /** * Get current year */ function getCurrentYear() { return new Date().getFullYear().toString(); } /** * Check if text contains any year placeholder */ function containsYearPlaceholder(text) { if (!text) return false; return YEAR_PLACEHOLDERS.some(placeholder => text.includes(placeholder)); } /** * Replace all year placeholders in text */ function replaceYearPlaceholders(text) { if (!text) return text; const year = getCurrentYear(); let result = text; YEAR_PLACEHOLDERS.forEach(placeholder => { try { result = result.replaceAll(placeholder, year); } catch (e) { // Fallback for older browsers result = result.split(placeholder).join(year); } }); return result; } /** * Check if element should be skipped (React components, scripts, etc.) */ function shouldSkipElement(element) { if (!element || !element.parentNode) { return true; } // Skip script, style, and noscript tags const tagName = element.tagName?.toLowerCase(); if (tagName === 'script' || tagName === 'style' || tagName === 'noscript') { return true; } // Skip React components (elements with data-initialized or data-reactroot) if (element.hasAttribute && ( element.hasAttribute('data-initialized') || element.hasAttribute('data-reactroot') || element.closest('[data-initialized]') || element.closest('[data-reactroot]') )) { return true; } // Skip if element is inside a React root let parent = element.parentNode; while (parent && parent !== document.body) { if (parent.hasAttribute && ( parent.hasAttribute('data-initialized') || parent.hasAttribute('data-reactroot') )) { return true; } parent = parent.parentNode; } return false; } /** * Safely replace {current.year} in text nodes using TreeWalker * Enhanced to handle rich text fields in Webflow blog posts */ function replaceYearInTextNodes(rootElement) { if (!rootElement) { return; } const year = getCurrentYear(); // Use a more aggressive TreeWalker that processes all text nodes const walker = document.createTreeWalker( rootElement, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, { acceptNode: function(node) { // For text nodes if (node.nodeType === Node.TEXT_NODE) { // Skip if node is inside a skipped element if (shouldSkipElement(node.parentElement)) { return NodeFilter.FILTER_REJECT; } // Only process nodes that contain any placeholder if (containsYearPlaceholder(node.textContent)) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_REJECT; } // For element nodes, continue traversing if (node.nodeType === Node.ELEMENT_NODE) { // Skip if it's a skipped element if (shouldSkipElement(node)) { return NodeFilter.FILTER_REJECT; } // Continue traversing children return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_REJECT; } }, false ); const textNodes = []; let node; // Collect all matching text nodes first while (node = walker.nextNode()) { if (node.nodeType === Node.TEXT_NODE && containsYearPlaceholder(node.textContent)) { textNodes.push(node); } } // Replace in collected nodes textNodes.forEach(textNode => { if (containsYearPlaceholder(textNode.textContent)) { textNode.textContent = replaceYearPlaceholders(textNode.textContent); } }); } /** * Find and process Webflow rich text fields specifically */ function processRichTextFields() { // Webflow rich text fields may have these classes or attributes const richTextSelectors = [ '.w-richtext', '.rich-text', '.funraise-rich-content', '.blog-content', '.blog-content-box', '[data-wf-field]', '.w-dyn-list', '.w-dyn-item', '[id*="toc-content"]', '[class*="richtext"]', '[class*="rich-content"]' ]; richTextSelectors.forEach(selector => { try { const elements = document.querySelectorAll(selector); elements.forEach(element => { if (!shouldSkipElement(element)) { replaceYearInTextNodes(element); } }); } catch (e) { // Skip invalid selectors } }); } /** * Debounce function to limit how often replacement runs */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * Initialize year replacement */ function initYearReplacement() { // Replace on initial page load replaceYearInTextNodes(document.body); // Process Webflow rich text fields specifically processRichTextFields(); // Set up MutationObserver for dynamically added content const observer = new MutationObserver(debounce((mutations) => { let shouldProcess = false; mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { // Only process element nodes if (node.nodeType === Node.ELEMENT_NODE) { // Skip if it's a React component if (!shouldSkipElement(node)) { replaceYearInTextNodes(node); shouldProcess = true; } } else if (node.nodeType === Node.TEXT_NODE) { // Handle text nodes that are added directly if (!shouldSkipElement(node.parentElement)) { if (containsYearPlaceholder(node.textContent)) { node.textContent = replaceYearPlaceholders(node.textContent); shouldProcess = true; } } } }); } else if (mutation.type === 'characterData') { // Handle text content changes (important for rich text) if (mutation.target.nodeType === Node.TEXT_NODE) { if (!shouldSkipElement(mutation.target.parentElement)) { if (containsYearPlaceholder(mutation.target.textContent)) { mutation.target.textContent = replaceYearPlaceholders(mutation.target.textContent); shouldProcess = true; } } } } }); // If we processed mutations, also check rich text fields if (shouldProcess) { processRichTextFields(); } }, 100)); // Start observing with more aggressive settings for rich text observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: false }); } /** * Run replacement with multiple attempts for late-loading content */ function runWithRetries() { initYearReplacement(); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runWithRetries); } else { // DOM already loaded runWithRetries(); } // Multiple retries for late-loading content (especially rich text in blog posts) setTimeout(runWithRetries, 300); setTimeout(runWithRetries, 500); setTimeout(runWithRetries, 1000); setTimeout(runWithRetries, 2000); setTimeout(runWithRetries, 3000); setTimeout(runWithRetries, 5000); // Also listen for Webflow's custom events if available if (typeof window !== 'undefined') { window.addEventListener('load', runWithRetries); // Some Webflow CMS content loads after window.load setTimeout(runWithRetries, 4000); setTimeout(runWithRetries, 6000); setTimeout(runWithRetries, 8000); } // Additional check for blog posts - they can load very late if (typeof window !== 'undefined' && window.location.pathname.includes('/blog/')) { setTimeout(runWithRetries, 10000); setTimeout(runWithRetries, 15000); } })();