1//Mobile Slide Menu 2// document.addEventListener("DOMContentLoaded", function () { 3// const menuElement = document.getElementById("mobile-menu"); 4// const menu = new SlideMenu(menuElement, { 5// position: "right", 6// }); 7 8// menuElement.addEventListener("sm.back-after", function () { 9// //jQuery(".mobile-contact").show(); 10 11// jQuery("#main-cta-box").show(); 12// jQuery("#about-cta-box").hide(); 13// jQuery("#impact-cta-box").hide(); 14// jQuery("#resources-cta-box").hide(); 15// jQuery("#media-cta-box").hide(); 16// jQuery("#involved-cta-box").hide(); 17// }); 18// }); 19 20//Mobile Donate Menu 21// document.addEventListener("DOMContentLoaded", function () { 22// const menuElement = document.getElementById("mob-donate-menu"); 23// const menu = new SlideMenu(menuElement, { 24// position: "right", 25// }); 26// }); 27 28//Mega Menu Donate 29jQuery(document).ready(function ($) { 30 var $btn = $("#donate-mega-menu-btn"); 31 var $desktopMenu = $("#donate-mega-menu"); 32 var $mobileMenu = $("#donate-mega-menu-mobile"); 33 34 function isMobile() { 35 return window.matchMedia("(max-width: 1023px)").matches; 36 } 37 38 $btn.on("click", function (e) { 39 e.preventDefault(); 40 e.stopPropagation(); 41 42 var megaMenu = $(".max-mega-menu").data("maxmegamenu"); 43 if (megaMenu && typeof megaMenu.hideAllPanels === "function") { 44 megaMenu.hideAllPanels(); 45 } 46 47 if (isMobile()) { 48 $mobileMenu.toggleClass("is-open"); 49 $desktopMenu.slideUp(); 50 } else { 51 $desktopMenu.stop(true, true).slideToggle(); 52 $mobileMenu.removeClass("is-open"); 53 } 54 }); 55 56 // Close on mobile close button 57 $(".mob-nav-close").on("click", function (e) { 58 e.preventDefault(); 59 e.stopPropagation(); 60 $mobileMenu.removeClass("is-open"); 61 }); 62 63 // Prevent inside clicks from bubbling 64 $desktopMenu.add($mobileMenu).on("click", function (e) { 65 e.stopPropagation(); 66 }); 67 68 // Click outside to close 69 $(document).on("click", function () { 70 $desktopMenu.slideUp(); 71 $mobileMenu.removeClass("is-open"); 72 }); 73 74 // Reset on resize 75 $(window).on("resize", function () { 76 $desktopMenu.hide(); 77 $mobileMenu.removeClass("is-open"); 78 }); 79 80 $("#page-selector") 81 .on("keydown", function (e) { 82 // Handle Enter key for keyboard users 83 if (e.which === 13) { 84 const val = $(this).val(); 85 if (val) window.location.href = val; 86 } 87 }) 88 .on("change", function (e) { 89 const val = $(this).val(); 90 if (val) window.location.href = val; 91 }); 92 93 //Table responsive 94 $(".sc-table").basictable({ 95 breakpoint: 1023, 96 }); 97 98 // Selectors 99 const $menu = $("#slide-out-menu"); 100 const $overlay = $("#menu-overlay"); 101 const $body = $("body"); 102 103 // 1. Open Main Menu 104 $("#open-mobile-menu").on("click", function (e) { 105 e.preventDefault(); 106 $menu.addClass("is-open"); 107 $overlay.addClass("is-visible"); 108 $body.css("overflow", "hidden"); // Prevent background scroll 109 }); 110 111 // 2. Close Main Menu (Function) 112 function closeMenu() { 113 $menu.removeClass("is-open"); 114 $overlay.removeClass("is-visible"); 115 $body.css("overflow", ""); 116 117 // Reset sub-panels after transition to avoid visual glitch 118 setTimeout(function () { 119 $(".sub-panel").removeClass("is-active"); 120 }, 300); 121 } 122 123 // Close triggers 124 $("#close-mobile-menu, #menu-overlay").on("click", closeMenu); 125 126 // 3. Drill Down: Open Sub Panel 127 $(".submenu-trigger").on("click", function (e) { 128 e.preventDefault(); 129 // Find the sibling sub-panel and slide it in 130 $(this).next(".sub-panel").addClass("is-active"); 131 }); 132 133 // 4. Go Back: Click the Header Title 134 $(".back-trigger").on("click", function (e) { 135 e.preventDefault(); 136 // Remove active class from the closest parent panel 137 $(this).closest(".sub-panel").removeClass("is-active"); 138 }); 139}); 140 141jQuery(window).load(function () { 142 if (jQuery(".search-module-init").length > 0) { 143 jQuery(".search-module-init").click(function () { 144 jQuery(this).parent().addClass("active"); 145 jQuery(".search-field").focus(); 146 }); 147 148 jQuery(document).on("click", function (event) { 149 if (!jQuery(event.target).closest(".search-module").length) { 150 jQuery(".search-module").removeClass("active"); 151 } 152 event.stopPropagation(); 153 }); 154 } 155 156 //Donate Hero
157 if (jQuery(".cta-form").length > 0) { 158 jQuery(".cta-form").each(function () { 159 jQuery(this).on("submit", function (e) { 160 e.preventDefault(); 161 162 var frequency = jQuery(this).find("input[name='frequency']:checked").val(); 163 var radioAmount = jQuery(this).find('input[name="amount"]:checked').val(); 164 var otherAmount = jQuery(this).find('input[name="other-amount"]').val(); 165 var baseUrl = jQuery(this).data("url"); 166 167 var finalAmount = radioAmount; 168 if (otherAmount && otherAmount.length > 0) { 169 finalAmount = otherAmount; 170 } 171 172 var donateData = "amount=" + encodeURIComponent(finalAmount) + "&frequency=" + encodeURIComponent(frequency); 173 174 if (frequency !== "once" && frequency !== "one-time") { 175 donateData += "&recurring=recurring"; 176 } 177 178 var url = baseUrl + "?utm_source=menu&" + donateData; 179 180 window.open(url, "_blank"); 181 }); 182 }); 183 } 184 185 // Accessibility Scripts Starts 186 function getIframeSrc($iframe) { 187 return $iframe.attr("src") || $iframe.data("src") || $iframe.attr("data-src") || ""; 188 } 189 190 function getYouTubeVideoId(src) { 191 const match = src.match(/(?:youtube(?:-nocookie)?\.com\/(?:embed\/|watch\?v=)|youtu\.be\/)([^"&?/ ]{11})/i); 192 return match ? match[1] : ""; 193 } 194 195 function getEmbedProvider(src) { 196 if (/youtube(?:-nocookie)?\.com|youtu\.be/i.test(src)) { 197 return "youtube"; 198 } 199 200 if (/facebook\.com\/plugins\/video\.php/i.test(src)) { 201 return "facebook"; 202 } 203 204 return ""; 205 } 206 207 function setIframeTitle($iframe, title) { 208 if (!$iframe.attr("title")) { 209 $iframe.attr("title", title); 210 } 211 } 212 213 // Function to add accessible titles to video embeds. 214 function fetchIframeTitle(iframe) { 215 const $iframe = jQuery(iframe); 216 const src = getIframeSrc($iframe); // Support for lazyload data-src 217 218 if (!src || $iframe.attr("title")) { 219 return; 220 } 221 222 const provider = getEmbedProvider(src); 223 224 if (provider === "youtube") { 225 const videoId = getYouTubeVideoId(src); 226 227 if (!videoId) { 228 setIframeTitle($iframe, "YouTube video player"); 229 return; 230 } 231 232 const oEmbedUrl = `https://www.youtube.com/oembed?url=${encodeURIComponent("https://www.youtube.com/watch?v=" + videoId)}&format=json`; 233 $iframe.attr("title", "YouTube video player"); 234 235 jQuery 236 .getJSON(oEmbedUrl, function (data) { 237 if (data && data.title) { 238 $iframe.attr("title", data.title); 239 } 240 }) 241 .fail(function () { 242 // Fallback if the fetch fails (e.g. video is private) 243 setIframeTitle($iframe, "YouTube video player"); 244 }); 245 246 return; 247 } 248 249 if (provider === "facebook") { 250 setIframeTitle($iframe, "Facebook video player"); 251 } 252 } 253 254 // Backward-compatible wrapper for any existing calls. 255 function fetchYouTubeTitle(iframe) { 256 fetchIframeTitle(iframe); 257 } 258 259 function processIframe(iframe) { 260 if (iframe && iframe.tagName && iframe.tagName.toLowerCase() === "iframe") { 261 fetchIframeTitle(iframe); 262 } 263 } 264 265 // Run on existing iframes 266 jQuery("iframe").each(function () { 267 processIframe(this); 268 }); 269 270 // --- Lazyload Support --- 271 // Watch for lazyload src changes and iframes injected after page load. 272 const observer = new MutationObserver((mutations) => { 273 mutations.forEach((mutation) => { 274 if (mutation.type === "attributes" && (mutation.attributeName === "src" || mutation.attributeName === "data-src")) { 275 processIframe(mutation.target); 276 } 277 278 if (mutation.type === "childList") { 279 jQuery(mutation.addedNodes).each(function () { 280 processIframe(this); 281 jQuery(this).find("iframe").each(function () { 282 processIframe(this); 283 }); 284 }); 285 } 286 }); 287 }); 288 289 // Start watching the document for iframe lazy-loading triggers. 290 observer.observe(document.body, { 291 attributes: true, 292 attributeFilter: ["src", "data-src"], 293 childList: true, 294 subtree: true, 295 }); 296 297 // 1. Target all tables 298 // jQuery("table").each(function () { 299 // const $table = jQuery(this); 300 301 // // 2. Logic: Only add it if the table doesn't have a <caption> or <th> 302 // // Real data tables usually have headers; layout tables do not. 303 // if ($table.find("th").length === 0 && $table.find("caption").length === 0) { 304 // $table.attr("role", "presentation"); 305 // } 306 // }); 307 // Accessibility Scripts Ends 308}); 309 310jQuery(document).ready(function ($) { 311 //mobile menu bottom boxes 312 jQuery(".about-link").click(function () { 313 jQuery("#main-cta-box").hide(); 314 jQuery("#about-cta-box").show(); 315 jQuery("#impact-cta-box").hide(); 316 jQuery("#resources-cta-box").hide(); 317 jQuery("#media-cta-box").hide(); 318 jQuery("#involved-cta-box").hide(); 319 }); 320 jQuery(".impact-link").click(function () { 321 jQuery("#main-cta-box").hide(); 322 jQuery("#about-cta-box").hide(); 323 jQuery("#impact-cta-box").show(); 324 jQuery("#resources-cta-box").hide(); 325 jQuery("#media-cta-box").hide(); 326 jQuery("#involved-cta-box").hide(); 327 }); 328 jQuery(".resources-link").click(function () { 329 jQuery("#main-cta-box").hide(); 330 jQuery("#about-cta-box").hide(); 331 jQuery("#impact-cta-box").hide(); 332 jQuery("#resources-cta-box").show(); 333 jQuery("#media-cta-box").hide(); 334 jQuery("#involved-cta-box").hide(); 335 }); 336 jQuery(".media-link").click(function () { 337 jQuery("#main-cta-box").hide(); 338 jQuery("#about-cta-box").hide(); 339 jQuery("#impact-cta-box").hide(); 340 jQuery("#resources-cta-box").hide(); 341 jQuery("#media-cta-box").show(); 342 jQuery("#involved-cta-box").hide(); 343 }); 344 jQuery(".involved-link").click(function () { 345 jQuery("#main-cta-box").hide(); 346 jQuery("#about-cta-box").hide(); 347 jQuery("#impact-cta-box").hide(); 348 jQuery("#resources-cta-box").hide(); 349 jQuery("#media-cta-box").hide(); 350 jQuery("#involved-cta-box").show(); 351 }); 352 353 /* ------------------------------------ 354 SMOOTH SCROLL TO SECTION ON CLICK 355 ------------------------------------ */ 356 jQuery(document).on("click", 'a[href^="#"]', function (e) { 357 var id = jQuery(this).attr("href"); 358 var $id = $(id); 359 360 if ($id.length === 0) return; 361 362 e.preventDefault(); 363 364 var pos = $id.offset().top; 365 366 jQuery("html, body").animate({ scrollTop: pos - 100 }, 500); 367 }); 368 369 /* ------------------------------------ 370 ADD ACTIVE CLASS ON SCROLL 371 ------------------------------------ */ 372 // var sections = $("section[id]"); // all sections that have ID 373 // var navLinks = $(".report-sidebar a[href^='#']"); // your sidebar links 374 375 // $(window).on("scroll", function () { 376 // var scrollPos = $(window).scrollTop() + 120; // offset so section title is visible 377 378 // sections.each(function () { 379 // var top = $(this).offset().top; 380 // var bottom = top + $(this).outerHeight(); 381 382 // // Check if this section is in the viewport 383 // if (scrollPos >= top && scrollPos <= bottom) { 384 // var id = jQuery(this).attr("id"); 385 386 // navLinks.removeClass("active"); 387 // jQuery('.report-sidebar a[href="#' + id + '"]').addClass("active"); 388 // } 389 // }); 390 // }); 391 392 //Staff Popup 393 jQuery(".staff-btn").click(function () { 394 var target = jQuery(this).data("toggle"); 395 jQuery('[data-target="' + target + '"]').toggleClass("hide-modal"); 396 }); 397 jQuery(".close-btn").click(function () { 398 var target = jQuery(this).data("toggle"); 399 jQuery(".staff-popup").addClass("hide-modal"); 400 }); 401 402 //Latest News Slider 403 jQuery(".latest-news-items").slick({ 404 dots: false, 405 infinite: false, 406 slidesToShow: 3.5, 407 centerMode: false, 408 vertical: false, 409 responsive: [ 410 { 411 breakpoint: 1279, 412 settings: { 413 slidesToShow: 2.5, 414 }, 415 }, 416 { 417 breakpoint: 991, 418 settings: { 419 slidesToShow: 1.5, 420 }, 421 }, 422 { 423 breakpoint: 767, 424 settings: { 425 slidesToShow: 1, 426 }, 427 }, 428 ], 429 }); 430 431 //Testimonials 432 jQuery(".testimonial-row").slick({ 433 dots: true,
434 arrows: false, 435 infinite: false, 436 slidesToShow: 3, 437 //slidesToScroll: 3, 438 infinite: true, 439 cssEase: "linear", 440 autoplay: true, 441 autoplaySpeed: 4000, 442 centerMode: true, 443 centerPadding: "0px", 444 //initialSlide: 1, 445 responsive: [ 446 { 447 breakpoint: 1279, 448 settings: { 449 slidesToShow: 2, 450 }, 451 }, 452 { 453 breakpoint: 991, 454 settings: { 455 slidesToShow: 2, 456 }, 457 }, 458 { 459 breakpoint: 767, 460 settings: { 461 slidesToShow: 1, 462 }, 463 }, 464 ], 465 }); 466 467 // Hide all content on load 468 //jQuery('.presidential-candidates .pc-container').hide(); 469 470 // Toggle on trigger click 471 jQuery(document).on("click", ".presidential-candidates .pc-trigger", function () { 472 var section = jQuery(this).closest(".presidential-candidates"); 473 var content = section.find(".pc-container"); 474 475 section.toggleClass("active"); 476 content.slideToggle(); 477 }); 478 479 //FAQs 480 //Set default open/close settings 481 jQuery(".faq-content").hide(); //Hide/close all containers 482 //jQuery('.faq-title:first').addClass('active').next().show(); //Add "active" class to first trigger, then show/open the immediate next container 483 484 // --- Animation Function --- 485 function animateBars($context) { 486 $context.find(".pb-progress-container").each(function (index) { 487 var $wrapper = $(this); 488 var $bar = $wrapper.find(".pb-progress-bar"); 489 var targetPercent = $wrapper.data("target"); 490 491 // 1. Reset width to 0 immediately (so it can grow again) 492 $bar.css("width", "0%"); 493 494 // 2. Stop any running animations to prevent glitches 495 $bar.stop(); 496 497 // 3. Stagger and Animate 498 setTimeout(function () { 499 $bar.animate( 500 { 501 width: targetPercent + "%", 502 }, 503 { 504 duration: 1500, 505 easing: "swing", 506 }, 507 ); 508 }, index * 200); // 200ms delay between bars 509 }); 510 } 511 512 jQuery(".faq-title").click(function () { 513 if (jQuery(this).next().is(":hidden")) { 514 //If immediate next container is closed... 515 jQuery(".faq-title").removeClass("active").next().slideUp(); //Remove all .faq-title classes and slide up the immediate next container 516 jQuery(this).toggleClass("active").next().slideDown(); //Add .faq-title class to clicked trigger and slide down the immediate next container 517 518 var $contentArea = $(this).next(); 519 520 // Run animation strictly inside this content area 521 animateBars($contentArea); 522 } 523 var id = jQuery(this).attr("id"); 524 if (id) { 525 if (history.pushState) { 526 history.pushState(null, null, "#" + id); 527 } else { 528 location.hash = "#" + id; 529 } 530 } 531 return false; //Prevent the browser jump to the link anchor 532 }); 533 534 function openFaqFromHash() { 535 if (window.location.hash) { 536 var hash = window.location.hash; 537 538 var $faqTitle = jQuery(hash); 539 540 if ($faqTitle.length && $faqTitle.hasClass("faq-title")) { 541 // Open the target FAQ if it's closed 542 if ($faqTitle.next().is(":hidden")) { 543 // Close any other open FAQs 544 jQuery(".faq-title").not($faqTitle).removeClass("active").next().slideUp(); 545 $faqTitle.addClass("active").next().slideDown(); 546 animateBars($faqTitle.next()); 547 } 548 549 // Scroll to the FAQ 550 setTimeout(function () { 551 jQuery("html, body").animate( 552 { 553 scrollTop: $faqTitle.offset().top - 100, // 100px offset for header 554 }, 555 500, 556 ); 557 }, 100); 558 } 559 } 560 } 561 562 // Run on page load 563 openFaqFromHash(); 564 565 // Run on hash change 566 jQuery(window).on("hashchange", function () { 567 openFaqFromHash(); 568 }); 569 // --- Animation Function --- 570 571 //Side Pannel 572 jQuery(".floating-tab").click(function () { 573 jQuery(this).toggleClass("active"); 574 jQuery(".side-pannel").toggleClass("active"); 575 jQuery(".slde-panel-overlay").toggle(); 576 return !1; 577 }); 578 jQuery(".slide-panel-close").click(function () { 579 jQuery(".side-pannel").removeClass("active"); 580 jQuery(".floating-tab").removeClass("active"); 581 jQuery(".slde-panel-overlay").hide(); 582 return !1; 583 }); 584 jQuery(".slde-panel-overlay").click(function () { 585 jQuery(this).hide(); 586 jQuery(".side-pannel").removeClass("active"); 587 jQuery(".floating-tab").removeClass("active"); 588 return !1; 589 }); 590 591 //CountUp 592 // jQuery(".countup").each(function () { 593 // var size = jQuery(this).text().split(".")[1] ? jQuery(this).text().split(".")[1].length : 0; 594 // jQuery(this) 595 // .prop("Counter", 0) 596 // .animate( 597 // { 598 // Counter: jQuery(this).text(), 599 // }, 600 // { 601 // duration: 2000, 602 // step: function (func) { 603 // jQuery(this).text(parseFloat(func).toFixed(size)); 604 // }, 605 // } 606 // ); 607 // }); 608 609 //voter-outreach 610 inView(".voter-outreach-col").once("enter", function (el) { 611 // Add the counter-start class 612 jQuery(el).addClass("counter-start"); 613 // Initialize the counterUp plugin on the element 614 jQuery(".vo-number").counterUp({ 615 delay: 10, 616 time: 2500, 617 }); 618 }); 619 620 //2024 election report 621 // inView(".efforts-col").once("enter", function (el) { 622 // // Add the counter-start class 623 // jQuery(el).addClass("counter-start"); 624 // // Initialize the counterUp plugin on the element 625 // jQuery(".efforts-content strong").counterUp({ 626 // delay: 10, 627 // time: 2500, 628 // }); 629 // }); 630 631 //Arizona Report Gradient BG Counter 632 inView(".rwgb-column").once("enter", function (el) { 633 // Add the counter-start class 634 jQuery(el).addClass("counter-start"); 635 // Initialize the counterUp plugin on the element 636 jQuery(".ergb-count").counterUp({ 637 delay: 10, 638 time: 2000, 639 }); 640 }); 641 642 //Four Column Icons 643 inView(".four-column-icon-item").once("enter", function (el) { 644 // Add the counter-start class 645 jQuery(el).addClass("counter-start"); 646 // Initialize the counterUp plugin on the element 647 jQuery(".countup").counterUp({ 648 delay: 10, 649 time: 2000, 650 }); 651 }); 652 653 //Latest News Slider 654 jQuery(".latest-video-row").slick({ 655 dots: false, 656 arrows: true, 657 infinite: false, 658 slidesToShow: 3.7, // Show 4 full slides + half of the next one 659 slidesToScroll: 1, 660 centerMode: false, // Keep slides left-aligned 661 variableWidth: false, 662 prevArrow: '<button type="button" class="slick-prev"><i class="fa-solid fa-chevron-left"></i></button>', 663 nextArrow: '<button type="button" class="slick-next"><i class="far fa-angle-right"></i></button>', 664 responsive: [ 665 { 666 breakpoint: 1279, 667 settings: { 668 slidesToShow: 3.5, 669 }, 670 }, 671 { 672 breakpoint: 991, 673 settings: { 674 slidesToShow: 2.5, 675 }, 676 }, 677 { 678 breakpoint: 767, 679 settings: { 680 slidesToShow: 1, 681 dots: true,
682 arrows: false, 683 autoplay: true, 684 }, 685 }, 686 ], 687 }); 688 689 //Latest News Slider 690 jQuery(".ib-row-area").slick({ 691 dots: true, 692 arrows: true, 693 infinite: true, 694 slidesToShow: 3, // Show 4 full slides + half of the next one 695 slidesToScroll: 1, 696 autoplay: true, 697 autoplaySpeed: 4000, 698 responsive: [ 699 { 700 breakpoint: 991, 701 settings: { 702 slidesToShow: 2, 703 }, 704 }, 705 { 706 breakpoint: 767, 707 settings: "unslick", 708 }, 709 ], 710 }); 711 712 //Fullscreen Swiper for News Page 713 jQuery(".news-pro-left").slick({ 714 dots: true, 715 arrows: true, 716 infinite: true, 717 autoplay: true, 718 autoplaySpeed: 4000, 719 fade: true, 720 slidesToShow: 1, 721 centerMode: false, 722 vertical: false, 723 }); 724 725 //Sub Menu 726 jQuery(".mobile-burger-menu").click(function () { 727 jQuery(".acf-nav-menu").toggle(); 728 }); 729 730 //Story carousel 731 function initStoryCarousel() { 732 if (jQuery(window).width() >= 768) { 733 if (!jQuery(".story-carousel").hasClass("slick-initialized")) { 734 jQuery(".story-carousel").slick({ 735 infinite: true, 736 dots: true, 737 arrows: true, 738 variableWidth: false, 739 slidesToShow: 4, 740 slidesToScroll: 4, 741 responsive: [ 742 { 743 breakpoint: 1170, 744 settings: { 745 slidesToShow: 3, 746 slidesToScroll: 3, 747 arrows: false, 748 dots: true, 749 }, 750 }, 751 { 752 breakpoint: 1023, 753 settings: { 754 slidesToShow: 2, 755 slidesToScroll: 2, 756 arrows: false, 757 dots: true, 758 }, 759 }, 760 { 761 breakpoint: 767, 762 settings: "unslick", 763 }, 764 ], 765 }); 766 } 767 } else { 768 // Destroy slick below 768px 769 if (jQuery(".story-carousel").hasClass("slick-initialized")) { 770 jQuery(".story-carousel").slick("unslick"); 771 } 772 } 773 } 774 775 // Run on load 776 initStoryCarousel(); 777 778 // Run on resize 779 jQuery(window).on("resize", initStoryCarousel); 780 781 // Function to handle tab clicks 782 function activateMapTab($tab, updateHash) { 783 var index = $tab.index(); 784 var tabId = $tab.find("a").attr("data-tab-id"); // You can use this or data-title 785 786 if (updateHash && tabId) { 787 window.location.hash = tabId; 788 } 789 790 // Update content 791 jQuery("ul.mtabs li").removeClass("current"); 792 $tab.addClass("current"); 793 794 jQuery(".map-panes").hide(); 795 jQuery(".map-panes").eq(index).show(); 796 797 //render_td_title(); 798 799 // Redraw map after a short delay 800 setTimeout(function () { 801 jQuery(".mapsvg-wrap-all:visible").each(function (i, el) { 802 var id = jQuery(el).attr("data-map-id"); 803 MapSVG.getById(id).redraw(); 804 }); 805 }, 200); 806 } 807 808 function activateMapTabFromHash() { 809 var hash = window.location.hash.replace("#", ""); 810 811 if (!hash) { 812 return; 813 } 814 815 try { 816 hash = decodeURIComponent(hash); 817 } catch (e) {} 818 819 var $tab = jQuery("ul.mtabs li").filter(function () { 820 return jQuery(this).find("a").attr("data-tab-id") === hash; 821 }).first(); 822 823 if ($tab.length) { 824 if ($tab.hasClass("current") && jQuery(".map-panes").eq($tab.index()).is(":visible")) { 825 return; 826 } 827 828 activateMapTab($tab, false); 829 } 830 } 831 832 // Function to handle tab clicks 833 jQuery("ul.mtabs li").click(function () { 834 activateMapTab(jQuery(this), true); 835 836 return false; 837 }); 838 839 activateMapTabFromHash(); 840 jQuery(window).on("hashchange", activateMapTabFromHash); 841}); 842 843//Preloader 844document.addEventListener("DOMContentLoaded", () => { 845 initCanvasLoaders(); 846}); 847 848function initCanvasLoaders() { 849 const containers = document.querySelectorAll(".canvas-loader-container"); 850 851 // Stagger animation on scroll 852 const observer = new IntersectionObserver( 853 (entries) => { 854 entries.forEach((entry, index) => { 855 if (entry.isIntersecting) { 856 setTimeout(() => { 857 // Initialize the loader logic for this element 858 createResponsiveLoader(entry.target); 859 }, index * 200); 860 observer.unobserve(entry.target); 861 } 862 }); 863 }, 864 { threshold: 0.2 }, 865 ); 866 867 containers.forEach((container) => observer.observe(container)); 868} 869 870function createResponsiveLoader(container) { 871 const canvas = container.querySelector("canvas"); 872 const textEl = container.querySelector(".canvas-loader-text"); 873 const targetPercent = parseInt(container.dataset.percent, 10); 874 const color = container.dataset.color || "#3498db"; 875 const trackColor = "#eeeeee"; 876 877 let currentProgress = 0; // 0 to 1 878 let animationFrameId; 879 let startTime = null; 880 const duration = 1500; 881 882 // Function to Draw (Stateless drawing based on current dimensions) 883 function draw() { 884 // 1. Get current container dimensions 885 const rect = container.getBoundingClientRect(); 886 const size = rect.width; // Because aspect-ratio is 1, width == height 887 888 // Avoid drawing if hidden or 0 size 889 if (size === 0) return; 890 891 // 2. Dynamic Settings 892 const lineWidth = size / 40; // Thickness scales with size 893 const fontSize = size / 5; // Font size scales with size 894 895 // 3. Handle High DPI (Retina) 896 const dpr = window.devicePixelRatio || 1; 897 canvas.width = size * dpr; 898 canvas.height = size * dpr; 899 900 // Important: Canvas CSS width/height must match container 901 // (Already handled by CSS width: 100%, height: 100%) 902 903 const ctx = canvas.getContext("2d"); 904 ctx.scale(dpr, dpr); 905 906 // 4. Update Text Size 907 textEl.style.fontSize = `${fontSize}px`; 908 textEl.textContent = Math.round(targetPercent * currentProgress) + "%"; 909 910 // 5. Draw Geometry 911 const centerX = size / 2; 912 const centerY = size / 2; 913 const radius = (size - lineWidth) / 2; 914 915 ctx.clearRect(0, 0, size, size); 916 917 // Track 918 ctx.beginPath(); 919 ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); 920 ctx.strokeStyle = trackColor; 921 ctx.lineWidth = lineWidth; 922 ctx.stroke(); 923 924 // Progress Arc 925 const radians = ((targetPercent * currentProgress) / 100) * (2 * Math.PI); 926 if (radians > 0) { 927 ctx.beginPath(); 928 ctx.arc(centerX, centerY, radius, 0, radians);
929 ctx.strokeStyle = color; 930 ctx.lineWidth = lineWidth; 931 ctx.lineCap = "round"; 932 ctx.stroke(); 933 } 934 } 935 936 // Animation Loop 937 function step(timestamp) { 938 if (!startTime) startTime = timestamp; 939 const elapsed = timestamp - startTime; 940 let rawProgress = elapsed / duration; 941 942 if (rawProgress > 1) rawProgress = 1; 943 944 // Ease Out Cubic 945 currentProgress = 1 - Math.pow(1 - rawProgress, 3); 946 947 draw(); 948 949 if (rawProgress < 1) { 950 animationFrameId = requestAnimationFrame(step); 951 } 952 } 953 954 // Start Animation 955 animationFrameId = requestAnimationFrame(step); 956 957 // Responsive Handler: ResizeObserver 958 // This ensures if the div changes size (e.g. window resize), 959 // we redraw at the new correct resolution immediately. 960 const resizeObserver = new ResizeObserver(() => { 961 // We redraw using the *current* progress state. 962 // If animation is finished (progress=1), it keeps it crisp at 100% 963 // If animation is running, the loop handles it, but this ensures no flicker. 964 draw(); 965 }); 966 967 resizeObserver.observe(container); 968}
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.