1function DonorboxCookie (name, value, days){ 2 if (value) { 3 this.set(name, value, days); 4 } 5} 6 7if (!DonorboxCookie.prototype.set) { 8 DonorboxCookie.prototype.set = function (name, value, days) { 9 var expires = ""; 10 11 if (days) { 12 var date = new Date(); 13 date.setTime(date.getTime() + (days*24*60*60*1000)); 14 expires = "; expires=" + date.toUTCString(); 15 } 16 17 document.cookie = name + "=" + (value || "") + expires + "; path=/"; 18 } 19} 20 21if (!DonorboxCookie.prototype.get) { 22 DonorboxCookie.prototype.get = function (name) { 23 var cookieArr = document.cookie.split(";"); 24 25 for(var i = 0; i < cookieArr.length; i++) { 26 var cookiePair = cookieArr[i].split("="); 27 if(name == cookiePair[0].trim()) { 28 return decodeURIComponent(cookiePair[1]); 29 } 30 } 31 return null; 32 } 33}; 34const accordion_selectors = [ ".accordion_container details.accordion_item" ]; 35const ANIMATION_DURATION = 300; 36const EASING = "cubic-bezier(0.4, 0, 0.2, 1)"; 37 38function getContent(item) { 39 return item.querySelector('.accordion_item_content'); 40} 41 42function animateHeight(content, from, to, onDone) { 43 content.style.overflow = 'hidden'; 44 content.style.height = from + 'px'; 45 content.style.transition = `height ${ANIMATION_DURATION}ms ${EASING}, opacity ${ANIMATION_DURATION}ms ${EASING}`; 46 content.style.opacity = from === 0 ? '0' : '1'; 47 48 requestAnimationFrame(() => { 49 requestAnimationFrame(() => { 50 content.style.height = to + 'px'; 51 content.style.opacity = to === 0 ? '0' : '1'; 52 }); 53 }); 54 55 const cleanup = (event) => { 56 if (event.propertyName !== 'height') return; 57 content.removeEventListener('transitionend', cleanup); 58 content.style.transition = ''; 59 content.style.overflow = ''; 60 content.style.height = ''; 61 content.style.opacity = ''; 62 if (typeof onDone === 'function') onDone(); 63 }; 64 content.addEventListener('transitionend', cleanup); 65} 66 67function expand(item, onDone) { 68 const content = getContent(item); 69 if (!content) { 70 item.setAttribute('open', ''); 71 if (typeof onDone === 'function') onDone(); 72 return; 73 } 74 item.setAttribute('open', ''); 75 const target = content.scrollHeight; 76 animateHeight(content, 0, target, onDone); 77} 78 79function collapse(item, onDone) { 80 const content = getContent(item); 81 if (!content) { 82 item.removeAttribute('open'); 83 if (typeof onDone === 'function') onDone(); 84 return; 85 } 86 const start = content.scrollHeight; 87 animateHeight(content, start, 0, () => { 88 item.removeAttribute('open'); 89 if (typeof onDone === 'function') onDone(); 90 }); 91} 92 93function bindSmoothToggle(items, singleOpen) { 94 items.forEach(item => { 95 const summary = item.querySelector('summary'); 96 if (!summary || summary.dataset.smoothBound === '1') return; 97 summary.dataset.smoothBound = '1'; 98 99 summary.addEventListener('click', (event) => { 100 event.preventDefault(); 101 if (item.dataset.animating === '1') return; 102 item.dataset.animating = '1'; 103 104 const finish = () => { item.dataset.animating = ''; }; 105 106 if (item.hasAttribute('open')) { 107 collapse(item, finish); 108 } else { 109 if (singleOpen) { 110 items.forEach(other => { 111 if (other !== item && other.hasAttribute('open')) collapse(other); 112 }); 113 } 114 expand(item, finish); 115 } 116 }); 117 }); 118} 119 120function faqDefaultOpen(items, faqDefaultOpenIndex) { 121 const index = parseInt(faqDefaultOpenIndex, 10); 122 if (Number.isNaN(index)) return; 123 if (index > 0 && index <= items.length) { 124 items[index - 1].setAttribute('open', ''); 125 } 126} 127 128document.addEventListener('DOMContentLoaded', () => { 129 const section_selectors = [ ".accordion_container" ]; 130 const faqSections = document.querySelectorAll(section_selectors.join(',')); 131 if (faqSections.length === 0) return; 132 133 const items = document.querySelectorAll(accordion_selectors.join(',')); 134 if (items.length === 0) return; 135 136 let singleOpen = false; 137 faqSections.forEach(faqSection => { 138 if (faqSection.getAttribute('data-accordion-behavior') === 'single_open') { 139 singleOpen = true; 140 } 141 const faqDefaultOpenIndex = faqSection.getAttribute('data-accordion-open-index'); 142 if (faqDefaultOpenIndex) { 143 faqDefaultOpen(items, faqDefaultOpenIndex); 144 } 145 }); 146 147 bindSmoothToggle(items, singleOpen); 148}); 149/**
150 * Reusable carousel: configurable autoplay and slide interval via data attributes. 151 * Use on any container with [data-carousel]. Works with testimonials, case studies, pricing, etc. 152 * 153 * Data attributes: 154 * data-carousel - enables carousel behavior 155 * data-carousel-autoplay="true" - enable automatic sliding (default: false) 156 * data-carousel-interval="5000" - autoplay interval in ms (default: 5000) 157 * 158 * Expected DOM structure: 159 * [data-carousel] (use overflow: hidden on this or on .carousel__viewport) 160 * .carousel__track 161 * .carousel__slide (repeated) 162 * Optional: .carousel__prev, .carousel__next, .carousel-dot (per slide) 163 */ 164 165const DEFAULT_INTERVAL_MS = 5000; 166 167function parseBool(value) { 168 if (value == null || value === "") return false; 169 return String(value).toLowerCase() === "true" || String(value) === "1"; 170} 171 172function parsePositiveInt(value, fallback) { 173 const n = parseInt(value, 10); 174 return Number.isFinite(n) && n > 0 ? n : fallback; 175} 176 177function initCarousel(root) { 178 const track = root.querySelector(".carousel__track"); 179 const slides = root.querySelectorAll(".carousel__slide"); 180 const prevBtn = root.querySelector(".carousel__prev"); 181 const nextBtn = root.querySelector(".carousel__next"); 182 const dots = root.querySelectorAll(".carousel-dot"); 183 184 if (!track || !slides.length) return null; 185 186 const total = slides.length; 187 let currentIndex = 0; 188 let autoplayTimer = null; 189 190 const autoplay = parseBool(root.dataset.carouselAutoplay); 191 const intervalMs = parsePositiveInt( 192 root.dataset.carouselInterval, 193 DEFAULT_INTERVAL_MS 194 ); 195 196 function goTo(index) { 197 currentIndex = ((index % total) + total) % total; 198 const offset = -currentIndex * 100; 199 track.style.transform = `translate3d(${offset}%, 0, 0)`; 200 dots.forEach((d, i) => d.classList.toggle("is-active", i === currentIndex)); 201 } 202 203 function next() { 204 goTo(currentIndex + 1); 205 } 206 207 function prev() { 208 goTo(currentIndex - 1); 209 } 210 211 function startAutoplay() { 212 stopAutoplay(); 213 autoplayTimer = setInterval(next, intervalMs); 214 } 215 216 function stopAutoplay() { 217 if (autoplayTimer) { 218 clearInterval(autoplayTimer); 219 autoplayTimer = null; 220 } 221 } 222 223 if (prevBtn) prevBtn.addEventListener("click", () => { stopAutoplay(); prev(); }); 224 if (nextBtn) nextBtn.addEventListener("click", () => { stopAutoplay(); next(); }); 225 226 dots.forEach((d, i) => d.addEventListener("click", () => { stopAutoplay(); goTo(i); })); 227 228 if (autoplay) startAutoplay(); 229 230 root.addEventListener("mouseenter", stopAutoplay); 231 root.addEventListener("mouseleave", () => { if (autoplay) startAutoplay(); }); 232 233 goTo(0); 234 return { goTo, next, prev, startAutoplay, stopAutoplay }; 235} 236 237function initAllCarousels() { 238 document.querySelectorAll("[data-carousel]").forEach((el) => { 239 if (!parseBool(el.dataset.carousel)) return; 240 initCarousel(el); 241 }); 242} 243 244document.addEventListener("DOMContentLoaded", initAllCarousels); 245function download_file(fileURL, fileName) { 246 // for non-IE 247 if (!window.ActiveXObject) { 248 var save = document.createElement('a'); 249 save.href = fileURL; 250 save.target = '_blank'; 251 var filename = fileURL.substring(fileURL.lastIndexOf('/')+1); 252 save.download = fileName || filename; 253 if ( navigator.userAgent.toLowerCase().match(/(ipad|iphone|safari)/) &&
254 navigator.userAgent.search("Chrome") < 0) { 255 document.location = save.href; 256 // window event not working here 257 }else{ 258 var evt = new MouseEvent('click', { 259 'view': window, 260 'bubbles': true, 261 'cancelable': false 262 }); 263 save.dispatchEvent(evt); 264 (window.URL || window.webkitURL).revokeObjectURL(save.href); 265 } 266 } 267 268 // for IE < 11 269 else if ( !! window.ActiveXObject && document.execCommand) { 270 var _window = window.open(fileURL, '_blank'); 271 _window.document.close(); 272 _window.document.execCommand('SaveAs', true, fileName || fileURL) 273 _window.close(); 274 } 275}; 276function copyToClipboard(text) { 277 if (navigator.clipboard && window.isSecureContext) { 278 return navigator.clipboard.writeText(text); 279 } 280} 281 282function showCopiedTooltip(button) { 283 var tooltipSpan = button.querySelector(".tooltip-text"); 284 if (!tooltipSpan) return; 285 286 var originalText = tooltipSpan.textContent; 287 tooltipSpan.textContent = "Copied!"; 288 289 setTimeout(function () { 290 tooltipSpan.textContent = originalText; 291 }, 2500); 292} 293 294document.addEventListener("DOMContentLoaded", function () { 295 // Select only copy buttons (must have data-url) 296 var copyButtons = document.querySelectorAll('.copy_link[data-url]'); 297 298 if (!copyButtons.length) return; 299 300 copyButtons.forEach(function (button) { 301 302 button.addEventListener("click", function (e) { 303 e.preventDefault(); 304 305 var url = button.getAttribute("data-url"); 306 if (!url) return; 307 308 copyToClipboard(url) 309 .then(function () { 310 showCopiedTooltip(button); 311 }) 312 .catch(function () { 313 console.warn("Copy failed"); 314 }); 315 }); 316 317 }); 318}); 319function validateEmail(value, form) { 320 const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 321 const trimmed = value ? value.trim() : null; 322 if (!trimmed) return form?.dataset?.required ?? null; 323 if (!EMAIL_REGEX.test(trimmed)) return form?.dataset?.validEmail ?? null; 324 return null; 325} 326 327function setMessage(el, text, isSuccess) { 328 if (!el) return; 329 el.textContent = text || ""; 330 el.classList.remove("success", "error"); 331 if (text) el.classList.add(isSuccess ? "success" : "error"); 332} 333 334function initSubscribeForm(form) { 335 const email = form.querySelector('input[name="email"]'); 336 const submitBtn = form.querySelector('input[type="submit"]'); 337 const messageEl = document.querySelector(".subscribe-form-message"); 338 const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute("content"); 339 const successMsg = form.dataset.subscribeSuccess || ""; 340 const errorMsg = form.dataset.subscribeError || ""; 341 342 function setSubmitting(submitting) { 343 if (!submitBtn) return; 344 submitBtn.disabled = submitting; 345 submitBtn.value = submitting ? submitBtn.dataset.waiting : submitBtn.dataset.buttonLabel; 346 } 347 348 function clearMessage() { 349 setMessage(messageEl, "", false); 350 } 351 352 form.addEventListener("submit", (e) => { 353 e.preventDefault(); 354 clearMessage(); 355 const emailValue = email && email.value ? email.value.trim() : ""; 356 const error = validateEmail(emailValue, form); 357 if (error) { 358 setMessage(messageEl, error, false); 359 return; 360 } 361 setSubmitting(true); 362 const formType = form.querySelector('input[name="form_type"]').value; 363 364 fetch(form.action, { 365 method: "POST", 366 headers: { "X-CSRF-Token": csrfToken, "Content-Type": "application/json", "Accept": "application/json" }, 367 body: JSON.stringify({ email: emailValue, form_type: formType }) 368 }) 369 .then((res) => { 370 const isSuccess = res.status === 200; 371 setMessage(messageEl, isSuccess ? successMsg : errorMsg, isSuccess); 372 373 if (isSuccess) { 374 form.reset(); 375 form.classList.add("hidden"); 376 } 377 }) 378 .catch(() => setMessage(messageEl, errorMsg, false)) 379 .finally(() => setSubmitting(false)); 380 }); 381 382 if (email) { 383 email.addEventListener("input", clearMessage); 384 email.addEventListener("blur", clearMessage); 385 } 386} 387 388document.addEventListener("DOMContentLoaded", function() { 389 document.querySelectorAll(".subscribe-form").forEach(initSubscribeForm); 390}); 391var scrollEventDebounce = null 392 393function headerScrollEvent () { 394 clearTimeout(scrollEventDebounce); 395 396 debounce = setTimeout(function() { 397 var docBody = document.body; //IE 'quirks' 398 var docElem = document.documentElement; //IE with doctype 399 docElem = (docElem.clientHeight) ? docElem : docBody; 400 401 if (typeof app_header !== 'undefined') { 402 if (docElem.scrollTop === 0) { 403 app_header.classList.remove('header_active'); 404 } else { 405 app_header.classList.add('header_active'); 406 } 407 } 408 409 },10); 410} 411 412window.addEventListener("scroll", headerScrollEvent, {passive: true}); 413const VideoPlayer = (function() { 414 const selectors = { 415 playButton: '.video_player_play_btn', 416 popup: '#video_popup',
417 iframes: '#video_popup iframe, .video_player_iframe' 418 }; 419 420 const config = { 421 maxWidth: 1024, 422 viewportRatio: 0.88, 423 aspectRatio: 9 / 16 424 }; 425 426 function createElement(tag, attributes) { 427 var element = document.createElement(tag); 428 Object.keys(attributes || {}).forEach(function(key) { 429 if (key === 'className') { 430 element.className = attributes[key]; 431 } else { 432 element.setAttribute(key, attributes[key]); 433 } 434 }); 435 return element; 436 } 437 438 function calculateDimensions() { 439 var viewportWidth = window.innerWidth * config.viewportRatio; 440 var width = Math.min(viewportWidth, config.maxWidth); 441 return { 442 width: width, 443 height: width * config.aspectRatio 444 }; 445 } 446 447 function getVideoPadding(iframe) { 448 var container = iframe.closest('.video_player_container'); 449 if (!container) return 0; 450 451 var padding = window.getComputedStyle(container).paddingLeft; 452 var value = parseFloat(padding); 453 454 return isNaN(value) ? 0 : value; 455 } 456 457 function setIframeSize() { 458 var iframes = document.querySelectorAll(selectors.iframes); 459 if (!iframes.length) return; 460 461 var dimensions = calculateDimensions(); 462 463 iframes.forEach(function(iframe) { 464 var padding = getVideoPadding(iframe); 465 var width = Math.max(dimensions.width - (padding * 2), 0); 466 var height = width * config.aspectRatio; 467 468 iframe.style.width = width + 'px'; 469 iframe.style.height = height + 'px'; 470 if (iframe.parentElement) { 471 iframe.parentElement.style.width = dimensions.width + 'px'; 472 } 473 }); 474 } 475 476 function createPopup(videoSrc) { 477 var wrapper = createElement('section'); 478 var popup = createElement('div', { id: 'video_popup' }); 479 var videoWrapper = createElement('div', { className: 'video-wrapper' }); 480 var iframe = createElement('iframe', { 481 id: 'popup_video_iframe', 482 src: videoSrc, 483 frameborder: '0', 484 allow: 'autoplay; fullscreen', 485 loading: 'lazy' 486 }); 487 var closeBtn = createElement('button', { id: 'popup_video_close' }); 488 489 videoWrapper.append(iframe, closeBtn); 490 popup.append(videoWrapper); 491 wrapper.append(popup); 492 document.body.append(wrapper); 493 494 popup.style.display = 'block'; 495 setIframeSize(); 496 497 closeBtn.addEventListener('click', function() { 498 popup?.parentElement?.remove(); 499 }); 500 } 501 502 function closePopupOnOutsideClick(event) { 503 if (event.target.id === 'video_popup') { 504 event.target.remove(); 505 } 506 } 507 508 function initPlayButton(button) { 509 button.addEventListener('click', function() { 510 var videoSrc = button.getAttribute('data-src'); 511 if (videoSrc) { 512 removeExistingPopups(); 513 createPopup(videoSrc); 514 } 515 }); 516 } 517 518 function removeExistingPopups() { 519 var popup = document.querySelectorAll(selectors.popup); 520 popup.forEach(function(popup) { 521 popup?.parentElement?.remove(); 522 }); 523 } 524 525 function init() { 526 var playButtons = document.querySelectorAll(selectors.playButton); 527 playButtons.forEach(initPlayButton); 528 529 window.addEventListener('click', closePopupOnOutsideClick); 530 window.addEventListener('load', setIframeSize); 531 window.addEventListener('resize', setIframeSize); 532 } 533 534 init(); 535 536 return { 537 setIframeSize: setIframeSize, 538 init: init 539 }; 540})(); 541 542document.addEventListener('turbo:load', VideoPlayer.init); 543document.addEventListener('turbo:frame-load', VideoPlayer.init); 544(function(window, document) { 545 const tabNavSelector = ".tabs__nav"; 546 const tabRadioSelector = 'input[type="radio"][name^="tab_"]'; 547 548 function getTabElements(radioButton) { 549 const tabId = radioButton.getAttribute("aria-controls"); 550 return { 551 tabContent: tabId ? document.getElementById(tabId) : null, 552 tabLabel: tabId ? document.getElementById(`${tabId}_label`) : null 553 }; 554 } 555 556 function hideTab(radioButton) { 557 const { tabContent, tabLabel } = getTabElements(radioButton); 558 559 if (tabContent) tabContent.classList.add("hidden"); 560 if (tabLabel) { 561 tabLabel.classList.remove("active"); 562 tabLabel.setAttribute("aria-selected", "false"); 563 } 564 } 565 566 function showTab(radioButton) { 567 const { tabContent, tabLabel } = getTabElements(radioButton); 568 569 if (tabContent) tabContent.classList.remove("hidden"); 570 if (tabLabel) { 571 tabLabel.classList.add("active"); 572 tabLabel.setAttribute("aria-selected", "true"); 573 } 574 } 575 576 function hideSiblingTabs(selectedRadioButton) { 577 const tabNav = selectedRadioButton.closest(tabNavSelector); 578 if (!tabNav) return; 579 580 tabNav.querySelectorAll(tabRadioSelector).forEach((radioButton) => { 581 if (radioButton !== selectedRadioButton) hideTab(radioButton); 582 }); 583 } 584 585 function handleTabChange(event) { 586 const radioButton = event.currentTarget; 587 if (!radioButton) return; 588 589 if (radioButton.checked) { 590 hideSiblingTabs(radioButton); 591 showTab(radioButton); 592 } else { 593 hideTab(radioButton); 594 } 595 } 596 597 function initTabs() { 598 document.querySelectorAll(tabNavSelector).forEach((tabNav) => { 599 tabNav.querySelectorAll(tabRadioSelector).forEach((radioButton) => { 600 radioButton.addEventListener("change", handleTabChange); 601 602 if (radioButton.checked) { 603 hideSiblingTabs(radioButton); 604 showTab(radioButton); 605 } else { 606 hideTab(radioButton); 607 } 608 }); 609 }); 610 } 611 612 document.addEventListener("DOMContentLoaded", initTabs); 613})(window, document); 614document.addEventListener("DOMContentLoaded", () => { 615 const carouselContainers = document.querySelectorAll("[data-card-carousel=true]")
616 carouselContainers.forEach(carouselContainer => { 617 618 const carousel = carouselContainer.querySelector(".card_wrapper") 619 const items = carousel.querySelectorAll(".resource_card") 620 const carouselStyle = getComputedStyle(carousel) 621 const gap = parseInt((carouselStyle?.getPropertyValue("gap") ?? 16).replace("px", "")) 622 623 let index = 0 624 625 function itemWidth() { 626 return items[0].offsetWidth + gap 627 } 628 629 function itemsPerView() { 630 return Math.round(carousel.clientWidth / items[0].offsetWidth) 631 } 632 633 function autoSlide(carouselContainer) { 634 let timer = setTimeout(() => { 635 carouselContainer.querySelector(".next_nav").click(); 636 timer = autoSlide(carouselContainer); 637 }, 5000); 638 639 return { 640 timer, 641 clear: () => clearTimeout(timer) 642 }; 643 } 644 645 carouselContainer.querySelector(".next_nav").onclick = () => { 646 const maxIndex = items.length - itemsPerView() 647 648 index = index + 1 649 650 if (index > maxIndex) index = 0 651 652 carousel.scrollTo({ 653 left: index * itemWidth(), 654 behavior: "smooth" 655 }) 656 } 657 658 carouselContainer.querySelector(".prev_nav").onclick = () => { 659 const maxIndex = items.length - itemsPerView() 660 661 index = index - 1 662 663 if (index < 0) index = maxIndex 664 665 carousel.scrollTo({ 666 left: index * itemWidth(), 667 behavior: "smooth" 668 }) 669 } 670 671 let autoSlideObj; 672 if (parseBool(carouselContainer.dataset.cardCarouselAutoplay)) { 673 autoSlideObj = autoSlide(carouselContainer); 674 } else { 675 autoSlideObj?.clear(); 676 } 677 678 window.addEventListener("resize", () => { 679 index = 0 680 carousel.scrollTo({ left: 0, behavior: "smooth" }) 681 }) 682 }); 683}); 684document.addEventListener('scroll', (event) => { 685 const floatingCta = document.querySelector('.floating_cta'); 686 if (floatingCta) { 687 const scrollOffset = floatingCta.dataset.scrollOffset; 688 const scrollPosition = event.target.documentElement.scrollTop || event.target.body.scrollTop; 689 690 if (scrollPosition > scrollOffset) { 691 floatingCta.classList.add('visible'); 692 } else { 693 floatingCta.classList.remove('visible'); 694 } 695 } 696}); 697(function () { 698 const UTM_PARAMS = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content','utm_org', 'keyword', 'original_url']; 699 const ADS_PARAMS = ['gclid','li_fat_id']; 700 const ALL_PARAMS = [...UTM_PARAMS, ...ADS_PARAMS]; 701 const INTERNAL_HOSTS = ['donorbox.org', 'donatetools.com']; 702 const IGNORED_PREFIXES = ['#', 'mailto:', 'tel:', 'javascript:']; 703 const fromUrl = new URLSearchParams(location.search); 704 const fromCookie = Object.fromEntries( 705 document.cookie.split('; ').map(cookie => { 706 const [key, ...rest] = cookie.split('='); 707 708 const value = decodeURIComponent( 709 rest.join('=').replace(/\+/g, ' ') 710 ); 711 712 return [key, value]; 713 }) 714 ); 715 const forward = {}; 716 const hasUtmSourceCookie = Boolean(fromCookie['utm_source']); 717 const pageSlug = document.body.dataset.landingPageSlug; 718 const attributionParamsPresent = document.body.dataset.attributionParamsPresent === "true"; 719 const allowedParams = hasUtmSourceCookie ? ALL_PARAMS : ADS_PARAMS; 720 allowedParams.forEach(k => { 721 const v = fromUrl.get(k) || fromCookie[k]; 722 if (v) forward[k] = v; 723 }); 724 725 function forwardAttributionParams(url, linkEl) { 726 const u = new URL(url, location.origin); 727 if (u.host && !INTERNAL_HOSTS.includes(u.host) && u.origin !== location.origin) return url; 728 if (IGNORED_PREFIXES.some(prefix => url.startsWith(prefix))) return url; 729 730 if (attributionParamsPresent) { 731 // existing forward map (paid UTMs + ads) 732 Object.entries(forward).forEach(([k, v]) => { 733 if (!u.searchParams.has(k)) u.searchParams.set(k, v); 734 }); 735 } else { 736 // ads only (existing) 737 ADS_PARAMS.forEach(k => { 738 const v = fromUrl.get(k) || fromCookie[k]; 739 if (v && !u.searchParams.has(k)) u.searchParams.set(k, v); 740 }); 741 // organic landing-page defaults 742 if (pageSlug) { 743 if (!u.searchParams.has("utm_source")) u.searchParams.set("utm_source", "donorbox-website"); 744 if (!u.searchParams.has("utm_medium")) u.searchParams.set("utm_medium", "landing-page"); 745 if (!u.searchParams.has("utm_campaign")) u.searchParams.set("utm_campaign", pageSlug); 746 } 747 const ctaPosition = linkEl?.dataset?.ctaPosition; 748 if (ctaPosition && !u.searchParams.has("utm_content")) u.searchParams.set("utm_content", ctaPosition); 749 } 750 751 return u.toString(); 752 } 753 754 document.addEventListener("DOMContentLoaded", () => { 755 document.querySelectorAll('a[href]').forEach(a => { 756 a.href = forwardAttributionParams(a.href, a); 757 }); 758 }); 759 760 document.addEventListener("turbo:frame-load", (event) => { 761 const frame = event.target; 762 frame.querySelectorAll('a[href]').forEach(a => { 763 a.href = forwardAttributionParams(a.href, a); 764 }); 765 }); 766 767 function decorateLinksIn(root) { 768 if (!root || typeof root.querySelectorAll !== 'function') return; 769 root.querySelectorAll('a[href]').forEach(a => { 770 a.href = forwardAttributionParams(a.href, a); 771 }); 772 } 773 774 document.addEventListener("turbo:before-stream-render", (event) => { 775 const originalRender = event.detail.render; 776 777 event.detail.render = async function (streamElement) { 778 await originalRender(streamElement); 779 780 const targets = typeof streamElement.targetElements === 'function' 781 ? streamElement.targetElements() 782 : (streamElement.targetElements || []); 783
784 targets.forEach(decorateLinksIn); 785 }; 786 }); 787})(); 788(function () { 789 'use strict'; 790 791 const MODAL_OPEN_CLASS = 'modal-open'; 792 const TRIGGER_SELECTOR = '[data-modal-open]'; 793 const MODAL_SELECTOR = '.popup_modal'; 794 const CLOSE_SELECTOR = '.close'; 795 796 function openModal(modal) { 797 if (!modal) return; 798 799 modal.style.display = 'flex'; 800 modal.style.display = '-ms-flexbox'; 801 document.body.classList.add(MODAL_OPEN_CLASS); 802 803 document.addEventListener('keydown', handleEscape); 804 } 805 806 function closeModal(modal) { 807 if (!modal) return; 808 809 modal.style.display = 'none'; 810 document.body.classList.remove(MODAL_OPEN_CLASS); 811 812 document.removeEventListener('keydown', handleEscape); 813 } 814 815 function handleEscape(event) { 816 if (event.key !== 'Escape') return; 817 818 document.querySelectorAll(MODAL_SELECTOR).forEach(function (modal) { 819 if (modal.style.display !== 'none' && modal.style.display !== '') { 820 closeModal(modal); 821 } 822 }); 823 } 824 825 function bindTriggers() { 826 document.querySelectorAll(TRIGGER_SELECTOR).forEach(function (trigger) { 827 trigger.addEventListener('click', function (event) { 828 event.preventDefault(); 829 var modal = document.getElementById(trigger.dataset.modalId); 830 openModal(modal); 831 }); 832 }); 833 } 834 835 function bindCloseButtons() { 836 document.querySelectorAll(MODAL_SELECTOR + ' ' + CLOSE_SELECTOR).forEach(function (closeBtn) { 837 closeBtn.addEventListener('click', function () { 838 var modal = closeBtn.closest(MODAL_SELECTOR); 839 closeModal(modal); 840 }); 841 }); 842 } 843 844 function bindOverlayClose() { 845 window.addEventListener('click', function (event) { 846 if (event.target.classList.contains('popup_modal')) { 847 closeModal(event.target); 848 } 849 }); 850 } 851 852 function init() { 853 bindTriggers(); 854 bindCloseButtons(); 855 bindOverlayClose(); 856 } 857 858 if (document.readyState === 'loading') { 859 document.addEventListener('DOMContentLoaded', init); 860 } else { 861 init(); 862 } 863})(); 864document.addEventListener("DOMContentLoaded", () => { 865 const marqueeCarousels = document.querySelectorAll("[data-marquee-carousel]"); 866 867 marqueeCarousels.forEach(marqueeCarousel => { 868 const marqueeTrack = marqueeCarousel.querySelector(".marquee_track"); 869 const marqueeItems = marqueeTrack.querySelector("div"); 870 marqueeItems.innerHTML += marqueeItems.innerHTML; 871 }); 872}); 873(function () { 874 'use strict'; 875 876 const SCRIPT_ID = 'demio-js';
877 const SCRIPT_SRC = 'https://cdn.demio.com/production/dashboard/embed.bundle.js'; 878 const CONTAINER_SELECTOR = '.demio_form_container'; 879 const STORYBLOK_BODY_ID = 'storyblok-body'; 880 881 // Demio's embed bundle scans the DOM and mounts forms on first execution 882 // only. In Storyblok preview mode the bridge replaces #storyblok-body via 883 // innerHTML on every edit, so the already-executed bundle does not see the 884 // new placeholder. Re-inject the script tag â a fresh load causes the 885 // bundle to re-scan and mount the updated form. 886 function reloadDemioBundle() { 887 var existing = document.getElementById(SCRIPT_ID); 888 if (existing) existing.remove(); 889 890 var script = document.createElement('script'); 891 script.async = true; 892 script.id = SCRIPT_ID; 893 script.type = 'text/javascript'; 894 script.src = SCRIPT_SRC; 895 document.body.appendChild(script); 896 } 897 898 function containerWasAdded(mutations) { 899 for (var i = 0; i < mutations.length; i++) { 900 var added = mutations[i].addedNodes; 901 for (var j = 0; j < added.length; j++) { 902 var node = added[j]; 903 if (node.nodeType !== 1) continue; 904 if (node.matches && node.matches(CONTAINER_SELECTOR)) return true; 905 if (node.querySelector && node.querySelector(CONTAINER_SELECTOR)) return true; 906 } 907 } 908 return false; 909 } 910 911 function watchForPreviewUpdates() { 912 var body = document.getElementById(STORYBLOK_BODY_ID); 913 if (!body || typeof MutationObserver === 'undefined') return; 914 915 var observer = new MutationObserver(function (mutations) { 916 if (containerWasAdded(mutations)) reloadDemioBundle(); 917 }); 918 919 observer.observe(body, { childList: true, subtree: true }); 920 } 921 922 if (document.readyState === 'loading') { 923 document.addEventListener('DOMContentLoaded', watchForPreviewUpdates); 924 } else { 925 watchForPreviewUpdates(); 926 } 927})(); 928document.addEventListener('DOMContentLoaded', () => { 929 document.querySelectorAll('.image_grid_expand_btn').forEach(btn => { 930 btn.addEventListener('click', () => { 931 const container = btn.closest('.image_grid').querySelector('.image_grid_container'); 932 container.classList.remove('is_collapsed'); 933 container.classList.add('is_expanded'); 934 btn.remove() 935 }); 936 }); 937}); 938const TIMEZONE_MAP = { 939 ET: "America/New_York", 940 CT: "America/Chicago", 941 PT: "America/Los_Angeles", 942 CET: "Europe/Berlin", 943 AEST: "Australia/Sydney", 944}; 945 946const formatterFor = (timeZone) => 947 new Intl.DateTimeFormat("en-US", { 948 timeZone, 949 month: "long", 950 day: "numeric", 951 year: "numeric", 952 hour: "2-digit", 953 minute: "2-digit", 954 hour12: false, 955 }); 956 957const formatWebinarDate = (formatter, date) => 958 formatter.format(date).replace(" at ", " "); 959 960document.addEventListener("change", (e) => { 961 if (!e.target.matches("select[name='timezone']")) return; 962 963 const label = e.target.value || "ET"; 964 const zone = TIMEZONE_MAP[label] || TIMEZONE_MAP["ET"]; 965 const formatter = formatterFor(zone); 966 967 document.querySelectorAll(".webinar_details .webinar_date").forEach((el) => { 968 const iso = el.dataset.datetime; 969 if (!iso) return; 970 971 const date = new Date(iso); 972 if (Number.isNaN(date.getTime())) return; 973 974 el.innerHTML = 975 `${formatWebinarDate(formatter, date)} ` + 976 `<span class="webinar_timezone">${label}</span>`; 977 }); 978}); 979const caseStudyContainer = document.querySelector(".case_study_listing"); 980 981const observer = new MutationObserver((mutations) => { 982 mutations.forEach((mutation) => { 983 mutation.addedNodes.forEach((node) => { 984 if (node.nodeType === Node.ELEMENT_NODE && node.matches(".case_study_card")) { 985 VideoPlayer.init(); 986 } 987 }); 988 }); 989}); 990 991if (caseStudyContainer) { 992 observer.observe(caseStudyContainer, { 993 childList: true, 994 subtree: true 995 }); 996}; 997document.addEventListener("DOMContentLoaded", function() { 998 const languageSelector = document.querySelectorAll("select[name='language_selector']"); 999 if (languageSelector.length === 0) return; 1000 1001 languageSelector.forEach(function(select) { 1002 select.addEventListener("change", function() { 1003 window.location.href = select.value; 1004 }); 1005 }); 1006}); 1007(function(window, document) { 1008 const TOC_ROOT_SELECTOR = "[data-toc-enabled='true']"; 1009 const TOC_PANEL_SELECTOR = "[data-toc-panel='true']"; 1010 const TOC_SOURCE_SELECTOR = "[data-toc-source='true']"; 1011 const TOC_ITEMS_SELECTOR = "[data-toc-items='true']"; 1012 const TOC_ENTRY_SELECTOR = "[data-toc-title][data-toc-id]"; 1013 const TOC_LINK_SELECTOR = "[data-toc-link='true']";
1014 const TOC_DETAILS_SELECTOR = ".case_study_content__toc-details"; 1015 const TABLET_MEDIA_QUERY = "(max-width: 768px)"; 1016 const SCROLL_OFFSET = 140; 1017 1018 function syncTocDetailsState(root) { 1019 const details = root.querySelector(TOC_DETAILS_SELECTOR); 1020 if (!details) return; 1021 1022 const setOpenState = () => { 1023 if (window.matchMedia(TABLET_MEDIA_QUERY).matches) { 1024 details.removeAttribute("open"); 1025 } else { 1026 details.setAttribute("open", ""); 1027 } 1028 }; 1029 1030 setOpenState(); 1031 window.matchMedia(TABLET_MEDIA_QUERY).addEventListener("change", setOpenState); 1032 } 1033 1034 function setActiveLink(itemsList, id) { 1035 if (!id) return; 1036 1037 itemsList.querySelectorAll(TOC_LINK_SELECTOR).forEach(link => { 1038 link.classList.toggle("active", link.getAttribute("href") === `#${id}`); 1039 }); 1040 } 1041 1042 function createTocItem(title, id) { 1043 const listItem = document.createElement("li"); 1044 listItem.setAttribute("data-toc-item", "true"); 1045 1046 const link = document.createElement("a"); 1047 link.setAttribute("data-toc-link", "true"); 1048 link.href = `#${id}`; 1049 link.textContent = title; 1050 1051 listItem.appendChild(link); 1052 return listItem; 1053 } 1054 1055 function bindTocItemClicks(itemsList) { 1056 itemsList.querySelectorAll(TOC_LINK_SELECTOR).forEach(link => { 1057 link.addEventListener("click", (event) => { 1058 event.preventDefault(); 1059 1060 const targetId = link.getAttribute("href")?.slice(1); 1061 if (!targetId) return; 1062 1063 setActiveLink(itemsList, targetId); 1064 1065 const target = document.getElementById(targetId); 1066 if (target) { 1067 const targetRect = target.getBoundingClientRect(); 1068 const absoluteElementTop = window.pageYOffset + targetRect.top; 1069 const offset = 120; 1070 window.scrollTo({ 1071 top: absoluteElementTop - offset, 1072 behavior: "smooth" 1073 }); 1074 } 1075 }); 1076 }); 1077 } 1078 1079 function bindScrollSpy(itemsList, entries) { 1080 const sections = Array.from(entries).filter(entry => entry.id); 1081 if (sections.length === 0) return; 1082 1083 let ticking = false; 1084 1085 const updateActiveLink = () => { 1086 let activeId = sections[0].id; 1087 1088 sections.forEach(section => { 1089 if (section.getBoundingClientRect().top <= SCROLL_OFFSET) { 1090 activeId = section.id; 1091 } 1092 }); 1093 1094 setActiveLink(itemsList, activeId); 1095 }; 1096 1097 window.addEventListener("scroll", () => { 1098 if (ticking) return; 1099 1100 ticking = true; 1101 window.requestAnimationFrame(() => { 1102 updateActiveLink(); 1103 ticking = false; 1104 }); 1105 }, { passive: true }); 1106 1107 updateActiveLink(); 1108 } 1109 1110 function buildTocForRoot(root) { 1111 const panel = root.querySelector(TOC_PANEL_SELECTOR); 1112 const source = root.querySelector(TOC_SOURCE_SELECTOR); 1113 if (!panel || !source) return; 1114 1115 const entries = source.querySelectorAll(TOC_ENTRY_SELECTOR); 1116 if (entries.length === 0) return; 1117 1118 let itemsList = panel.querySelector(TOC_ITEMS_SELECTOR); 1119 if (!itemsList) { 1120 itemsList = document.createElement("ul"); 1121 itemsList.setAttribute("data-toc-items", "true"); 1122 panel.appendChild(itemsList); 1123 } 1124 1125 itemsList.innerHTML = ""; 1126 1127 entries.forEach(entry => { 1128 const title = entry.getAttribute("data-toc-title"); 1129 const id = entry.getAttribute("data-toc-id"); 1130 if (!title || !id) return; 1131 1132 if (!entry.id) entry.id = id; 1133 1134 itemsList.appendChild(createTocItem(title, id)); 1135 }); 1136 1137 bindTocItemClicks(itemsList); 1138 bindScrollSpy(itemsList, entries); 1139 syncTocDetailsState(root); 1140 } 1141 1142 function initTableOfContent() { 1143 document.querySelectorAll(TOC_ROOT_SELECTOR).forEach(buildTocForRoot); 1144 } 1145 1146 window.TableOfContent = { init: initTableOfContent }; 1147 document.addEventListener("DOMContentLoaded", initTableOfContent); 1148})(window, document); 1149function initNavBarIntent() { 1150 var sticky_nav_bar = document.getElementById("sticky_nav_bar"); 1151 1152 if (sticky_nav_bar) { 1153 var sticky_nav_bar_close = document.getElementById("sticky_nav_bar_close_icon"); 1154 var currentVersion = sticky_nav_bar.dataset.version; 1155 var dismissedVersion = new DonorboxCookie().get('sticky_nav_bar_version'); 1156 var isDismissed = new DonorboxCookie().get('sticky_nav_bar'); 1157 1158 function hideStickyNavBar() { 1159 new DonorboxCookie('sticky_nav_bar', true, 3); 1160 new DonorboxCookie('sticky_nav_bar_version', currentVersion, 3); 1161 sticky_nav_bar.classList.add("d-none"); 1162 } 1163 1164 function showStickyNavBar() { 1165 sticky_nav_bar.classList.remove("d-none"); 1166 } 1167 1168 if (sticky_nav_bar_close) { 1169 sticky_nav_bar_close.onclick = function () { 1170 hideStickyNavBar(); 1171 }; 1172 } 1173 1174 if (dismissedVersion === currentVersion && isDismissed) { 1175 sticky_nav_bar.classList.add("d-none"); 1176 } else { 1177 showStickyNavBar(); 1178 } 1179 } 1180} 1181 1182document.addEventListener("turbo:load", initNavBarIntent); 1183document.addEventListener("DOMContentLoaded", initNavBarIntent); 1184(function (window, document) { 1185 const ROOT_SELECTOR = "[data-crm-contact-tier-pricing]"; 1186 1187 function parseTiers(root) { 1188 try { 1189 const tiers = JSON.parse(root.dataset.tiers || "[]"); 1190 return Array.isArray(tiers) ? tiers : []; 1191 } catch (_error) { 1192 return []; 1193 } 1194 } 1195 1196 function updateSliderFill(slider) { 1197 const min = Number(slider.min) || 0; 1198 const max = Number(slider.max) || 100; 1199 const value = ((Number(slider.value) - min) / (max - min || 1)) * 100; 1200 slider.style.background = 1201 "linear-gradient(to right, #128AED " + value + "%, #ABC1E3 " + value + "%)"; 1202 } 1203 1204 function setVisible(element, visible) { 1205 if (!element) return; 1206 if (visible) { 1207 element.removeAttribute("hidden"); 1208 element.style.display = ""; 1209 } else { 1210 element.setAttribute("hidden", "hidden"); 1211 element.style.display = "none"; 1212 } 1213 } 1214 1215 function updateBilling(root) { 1216 const slider = root.querySelector(".crm_contact_tier_pricing__range"); 1217 const tiers = parseTiers(root); 1218 if (!slider || tiers.length === 0) return; 1219 1220 const step = Number(slider.step) || 100; 1221 const index = Math.min(Math.round(Number(slider.value) / step), tiers.length - 1); 1222 const tier = tiers[index] || {}; 1223 const period = root.dataset.period || "monthly"; 1224 const monthlyInfo = root.querySelector('[data-billing-info="monthly"]'); 1225 const annualInfo = root.querySelector('[data-billing-info="annually"]'); 1226 const contactSales = root.querySelector("[data-contact-sales]"); 1227 1228 updateSliderFill(slider); 1229 slider.setAttribute("aria-valuenow", String(index)); 1230 1231 if (tier.show_contact_sales) { 1232 setVisible(monthlyInfo, false); 1233 setVisible(annualInfo, false); 1234 setVisible(contactSales, true); 1235 return; 1236 } 1237 1238 setVisible(contactSales, false); 1239 1240 if (period === "monthly") { 1241 const amount = monthlyInfo && monthlyInfo.querySelector("[data-price]"); 1242 if (amount) amount.textContent = tier.monthly_price || ""; 1243 setVisible(monthlyInfo, true); 1244 setVisible(annualInfo, false); 1245 } else { 1246 const amount = annualInfo && annualInfo.querySelector("[data-price]"); 1247 if (amount) amount.textContent = tier.annual_price || ""; 1248 setVisible(monthlyInfo, false); 1249 setVisible(annualInfo, true); 1250 } 1251 } 1252
1253 function switchPeriod(root, period) { 1254 root.dataset.period = period; 1255 root.querySelectorAll(".tab_button").forEach((button) => { 1256 const active = button.dataset.period === period; 1257 button.classList.toggle("active", active); 1258 button.setAttribute("aria-selected", active ? "true" : "false"); 1259 }); 1260 updateBilling(root); 1261 } 1262 1263 function initRoot(root) { 1264 if (root.dataset.initialized === "true") return; 1265 root.dataset.initialized = "true"; 1266 root.dataset.period = root.dataset.period || "monthly"; 1267 1268 const slider = root.querySelector(".crm_contact_tier_pricing__range"); 1269 if (slider) { 1270 slider.addEventListener("input", () => updateBilling(root)); 1271 } 1272 1273 root.querySelectorAll(".tab_button").forEach((button) => { 1274 button.addEventListener("click", () => { 1275 switchPeriod(root, button.dataset.period || "monthly"); 1276 }); 1277 }); 1278 1279 updateBilling(root); 1280 } 1281 1282 function initCrmContactTierPricing() { 1283 document.querySelectorAll(ROOT_SELECTOR).forEach(initRoot); 1284 } 1285 1286 document.addEventListener("DOMContentLoaded", initCrmContactTierPricing); 1287 document.addEventListener("turbo:load", initCrmContactTierPricing); 1288})(window, document); 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310
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.