1(function ($) { 2 $(document).ready(function () { 3 $(".location-settings").click(function (e) { 4 $(".location-switcher").toggle(); 5 e.preventDefault(); 6 }); 7 $(".toggle-timeline-event").click(function (e) { 8 e.preventDefault(); 9 $(".toggle-timeline-event").removeClass("active"); 10 $(this).addClass("active"); 11 $(".timeline-event").hide(); 12 $("[data-year-id=" + $(this).data("year-id") + "]").show(); 13 }); 14 15 // ------------------- 16 // HERO CAROUSEL 17 // ------------------- 18 19 if ($(".hero-carousel").length) { 20 $(".hero-carousel>.hero-carousel-slides").slick({ 21 dots: true, 22 infinite: true, 23 speed: 600, 24 slidesToShow: 1, 25 slidesToScroll: 1, 26 arrows: true, 27 autoplay: true, 28 }); 29 } 30 31 // ------------------- 32 // SIMPLE CAROUSEL 33 // ------------------- 34 35 if ($(".carousel").length) { 36 // Carousel layout if less than 2 items 37 $(".carousel>.slides-length-small").slick({ 38 dots: true, 39 arrows: true, 40 infinite: true, 41 speed: 600, 42 slidesToShow: 1, 43 slidesToScroll: 1, 44 centerMode: false, 45 initialSlide: 0, 46 autoplay: true, 47 responsive: [ 48 { 49 breakpoint: 768, 50 settings: { 51 arrows: false, 52 }, 53 }, 54 ], 55 }); 56 57 // Carousel layout if greater than 2 but less than 4 items 58 $(".carousel>.slides-length-medium").slick({ 59 dots: true, 60 arrows: true, 61 infinite: true, 62 speed: 600, 63 slidesToShow: 2, 64 slidesToScroll: 1, 65 centerMode: false, 66 initialSlide: 0, 67 autoplay: true, 68 responsive: [ 69 { 70 breakpoint: 768, 71 settings: { 72 slidesToShow: 1, 73 slidesToScroll: 1, 74 arrows: false, 75 }, 76 }, 77 ], 78 }); 79 80 // Carousel layout if greater than 4 items 81 $(".carousel>.slides-length-large").slick({ 82 dots: true, 83 arrows: true, 84 infinite: true, 85 speed: 600, 86 slidesToShow: 4, 87 slidesToScroll: 1, 88 centerMode: false, 89 initialSlide: 0, 90 autoplay: true, 91 responsive: [ 92 { 93 breakpoint: 1280, 94 settings: { 95 slidesToShow: 3, 96 slidesToScroll: 1, 97 }, 98 }, 99 { 100 breakpoint: 850, 101 settings: { 102 slidesToShow: 2, 103 slidesToScroll: 1, 104 }, 105 }, 106 { 107 breakpoint: 768, 108 settings: { 109 slidesToShow: 1, 110 slidesToScroll: 1, 111 arrows: false, 112 }, 113 }, 114 ], 115 }); 116 } 117 118 // ------------------- 119 // CARD CAROUSEL 120 // ------------------- 121 122 if ($(".card-carousel").length) { 123 $(".card-carousel .card-carousel__slides").slick({ 124 dots: true, 125 arrows: true, 126 infinite: true, 127 speed: 500, 128 variableWidth: true, 129 slidesToScroll: 1, 130 centerMode: false, 131 initialSlide: 0, 132 autoplay: false, 133 responsive: [ 134 { 135 breakpoint: 768, 136 settings: { 137 variableWidth: true, 138 slidesToScroll: 1, 139 arrows: false, 140 infinite: true, 141 }, 142 }, 143 ], 144 }); 145 } 146 147 // ------------------- 148 // DISCOVER SLIDER 149 // ------------------- 150 if ($(".discover").length) { 151 // Carousel layout if less than 2 items 152 $(".discover-cards").slick({ 153 dots: true, 154 arrows: true, 155 infinite: true, 156 speed: 300, 157 slidesToShow: 3, 158 slidesToScroll: 1, 159 centerMode: false, 160 initialSlide: 0, 161 autoplay: false, 162 responsive: [ 163 { 164 breakpoint: 1023, 165 settings: { 166 slidesToShow: 2, 167 slidesToScroll: 1, 168 arrows: false, 169 }, 170 }, 171 { 172 breakpoint: 768, 173 settings: { 174 slidesToShow: 1,
175 arrows: false, 176 }, 177 }, 178 ], 179 }); 180 } 181 182 let $tags = $(".discover-tag-toggle"); 183 let discoverParent = document.querySelector(".discover-carousel"); 184 185 $(".discover-tag-toggle").on("click", function () { 186 let isActive = $(this).attr("data-color") === "white"; 187 188 $tags.not($(this)).attr("data-color", "white"); 189 $(this).attr("data-color", isActive ? "green" : "white"); 190 191 let currentFilter = isActive ? $(this).data("tag") : null; 192 193 if (currentFilter) { 194 discoverParent.classList.add("show"); 195 $(".discover-cards").slick("slickUnfilter"); 196 $(".discover-cards").slick("slickFilter", function () { 197 let filterCard = false; 198 let cardTags = $(this).data("tag"); 199 200 if (cardTags.includes(`${currentFilter}`)) { 201 filterCard = true; 202 } 203 204 return filterCard; 205 }); 206 } else { 207 discoverParent.classList.remove("show"); 208 } 209 }); 210 211 // ------------------- 212 // ANIMATED PARALLEL 213 // ------------------- 214 if ($(".animated-parallel").length) { 215 function animatedParallel() { 216 const parent = document.querySelectorAll(".animated-parallel"); 217 const arr = [...parent]; 218 219 arr.forEach((el) => { 220 const dataAttr = el.getAttribute("data-scroll-trigger"); 221 222 if (dataAttr == "true") { 223 return createAnimatedParallelObserver(el); 224 } else { 225 const button = el.querySelector(".block-heading button"); 226 if (button) { 227 button.addEventListener("click", () => { 228 el.classList.add("active"); 229 }); 230 } 231 } 232 233 // Active animation if element is empty and intersecting 234 function createAnimatedParallelObserver(element) { 235 let options = { threshold: 1 }; 236 const aniParObserver = new IntersectionObserver( 237 handleAnimatedParallelObserver, 238 options, 239 ); 240 241 aniParObserver.observe(element); 242 } 243 244 function handleAnimatedParallelObserver(entries, options) { 245 entries.forEach((entry) => { 246 if (entry.isIntersecting) { 247 entry.target.classList.add("active"); 248 } 249 }); 250 } 251 }); 252 } 253 animatedParallel(); 254 } 255 256 // ------------------- 257 // INFO BANNER - COUNTER 258 // ------------------- 259 if ($(".info-banner").length) { 260 function infoBanner() { 261 // Animate numbers 262 function createInfoBannerObserver() { 263 let options = { 264 threshold: 1, 265 }; 266 267 const infoBannerObserver = new IntersectionObserver( 268 handleInfoBannerIntersect, 269 options, 270 ); 271 const bannerContainers = document.querySelectorAll(".info-banner"); 272 let arr = Array.from(bannerContainers); 273 274 // Set observer to observe each banner 275 arr.forEach((banner) => { 276 infoBannerObserver.observe(banner); 277 }); 278 } 279 createInfoBannerObserver(); 280 281 function handleInfoBannerIntersect(entries, options) { 282 let speed = 10; 283 entries.forEach((entry) => { 284 if (entry.isIntersecting) { 285 const counterValues = entry.target.querySelectorAll( 286 ".info-banner-items .info-banner-content-item .counter span", 287 ); 288 289 counterValues.forEach((counterValue) => { 290 setMinWidth(counterValue.closest(".info-banner-content-item")); 291 animateCountUp(counterValue); 292 }); 293 294 function setMinWidth(el) { 295 el.style.minWidth = `${el.offsetWidth}px`; 296 } 297 298 function animateCountUp(el) { 299 let counterValue = el.getAttribute("data-count-to"); 300 let isCommaNum = false; 301 let countFrom = 0; 302 let counterFixed = 0; 303 let decimals = 0; 304 305 if (counterValue.includes(",")) { 306 // comma 307 let numberLength = counterValue.length - 1; 308 let indexDotComma = counterValue.indexOf(","); 309 decimals = numberLength - indexDotComma; 310 311 counterValue = counterValue.replace(",", "."); 312 313 isCommaNum = true; 314 } else if (counterValue.includes(".")) { 315 // dot 316 let numberLength = counterValue.length - 1; 317 let indexDotComma = counterValue.indexOf("."); 318 decimals = numberLength - indexDotComma; 319 } 320 321 const COUNTER_VALUE_NUM = parseInt(counterValue); 322 let plusVariabel = COUNTER_VALUE_NUM / 100; 323 324 let interval = setInterval(countingStraight, speed); 325 326 function countingStraight() { 327 if ( 328 counterFixed >= COUNTER_VALUE_NUM || 329 countFrom >= COUNTER_VALUE_NUM 330 ) { 331 clearInterval(interval); 332 333 if (isCommaNum) { 334 counterValue = counterValue.replace(".", ","); 335 el.innerHTML = counterValue; 336 } else { 337 el.innerHTML = counterValue; 338 } 339 } else { 340 countFrom = countFrom + plusVariabel; 341 if (decimals >= 1) { 342 counterFixed = countFrom.toFixed(decimals); 343 el.innerHTML = counterFixed; 344 } else { 345 el.innerHTML = Math.ceil(countFrom); 346 } 347 } 348 } 349 } 350 } 351 }); 352 } 353 } 354 infoBanner(); 355 } 356 357 // ----------------------------- 358 // RESPONSIVE TABLES 359 // ----------------------------- 360 if ($(".text-formatted table").length) { 361 function mobileTables() { 362 // ------------------------------------------------------------ 363 // Variables 364 // - list of dom tables on page 365 // - mobile breakpoint where table display could change 366 // ------------------------------------------------------------ 367 const tableList = document.querySelectorAll(".text-formatted table"); 368 const breakpointMobile = 480; // 30rem 369 370 // ------------------------------------------------------------ 371 // Initialize tables 372 // ------------------------------------------------------------ 373 tableList.forEach((table) => { 374 createTableWrapper(table); 375 createTableRoles(table); 376 createTableTitles(table); 377 watchTableScroll(table); 378 }); 379 380 // ------------------------------------------------------------ 381 // Create table wrapper to handle overflow scroll 382 // sticky headers etc. 383 // ------------------------------------------------------------ 384 function createTableWrapper(table) { 385 const wrapper = document.createElement("div"); 386 wrapper.classList.add("table-wrapper"); 387 table.insertAdjacentElement("beforebegin", wrapper); 388 wrapper.append(table); 389 } 390 391 // ------------------------------------------------------------ 392 // Add table headings as data attributes to allow 393 // switching to grid/block layout 394 // ------------------------------------------------------------ 395 function createTableTitles(table) { 396 // Early exit 397 const headerRows = table.querySelectorAll("thead tr"); 398 const hasFooter = table.querySelector("tfoot"); 399 const hasBody = table.querySelector("tbody"); 400 401 if (headerRows.length !== 1 || hasFooter || !hasBody) return; 402 403 // Can have mobile layout 404 table.classList.add("can-be-mobile"); 405 406 const headerList = table.querySelectorAll("thead th"); 407 const rowList = [...table.querySelectorAll("tbody tr")]; 408 const tableList = rowList.map((row) => [ 409 ...row.querySelectorAll("td, th"),
410 ]); 411 412 // Exit if we have no table header cells 413 if (headerList.length === 0) return; 414 415 // In order to match spanned columns and rows the table is "normalized" 416 // by replacing missing cells with a clone of the original cell 417 // this is done in two steps - first replacing colspans then rowspans 418 // Since the original cells are linked to the dom we can then match 419 // cells by column index and apply data-title attributes 420 421 // Fill in missing cells based on colspan 422 tableList.forEach((rowList, rowIndex, tableList) => { 423 rowList.forEach((col, colIndex) => { 424 // number of colspans 425 const colSpan = parseInt(col.getAttribute("colspan")) || 1; 426 // create a clone of the table cell 427 const clone = col.cloneNode(true); 428 // remove colspan attribute on clone 429 clone.removeAttribute("colspan"); 430 // fill out the blanks 431 for (let c = 1; c < colSpan; ++c) { 432 tableList[rowIndex].splice(colIndex + c, 0, clone); 433 } 434 }); 435 }); 436 437 // Fill in missing cells based on rowspan 438 tableList.forEach((rowList, rowIndex, tableList) => { 439 rowList.forEach((col, colIndex) => { 440 // number of rowspans 441 const rowSpan = parseInt(col.getAttribute("rowspan")) || 1; 442 // create a clone of the table cell 443 const clone = col.cloneNode(true); 444 // remove colspan attribute on clone 445 clone.removeAttribute("rowspan"); 446 // fill out the blanks 447 for (let r = 1; r < rowSpan; ++r) { 448 tableList[rowIndex + r].splice(colIndex, 0, clone); 449 } 450 }); 451 }); 452 453 // Add matching header text as data attribute 454 tableList.forEach((rowList, rowIndex, tableList) => { 455 rowList.forEach((col, colIndex) => { 456 if (headerList[colIndex].innerHTML === " ") { 457 headerList[colIndex].innerHTML = ""; 458 } 459 460 const innerHTMLToString = headerList[colIndex].innerHTML 461 .toString() 462 .replace(/<[^>]*>/g, " ") 463 .replace(/\s{2,}/g, " ") 464 .trim(); 465 466 col.dataset.title = innerHTMLToString; 467 }); 468 }); 469 } 470 471 // ------------------------------------------------------------ 472 // Add ARIA roles to allow changing display type without 473 // messing up accessibility 474 // ------------------------------------------------------------ 475 function createTableRoles(table) { 476 const getNodes = (sel) => table.querySelectorAll(sel); 477 const setRole = (role) => (el) => el.setAttribute("role", role); 478 479 setRole("table")(table); 480 getNodes("caption").forEach(setRole("caption")); 481 getNodes("thead, tbody, tfoot").forEach(setRole("rowgroup")); 482 getNodes("tr").forEach(setRole("row")); 483 getNodes("td").forEach(setRole("cell")); 484 getNodes("th").forEach(setRole("columnheader")); 485 getNodes("th[scope=row]").forEach(setRole("rowheader")); 486 } 487 488 // ------------------------------------------------------------ 489 // Detect table scroll when the table exceeds the width 490 // of the wrapping container 491 // ------------------------------------------------------------ 492 function watchTableScroll(table) { 493 const root = table.parentNode; 494 let breakpointLocal; 495 496 const observer = new ResizeObserver((entries) => { 497 entries.forEach((entry) => { 498 // prevent ResizeObserver - loop limit exceeded 499 requestAnimationFrame(() => { 500 // detect scroll bý comparing scroll and offset width 501 const hasScroll = root.scrollWidth > root.offsetWidth; 502 503 // if scrolling is detected add scroll class and save 504 // the current window width as a local breakpoint 505 // the local breakpoint is used together with the 506 // mobile breakpoibt to avoid re-style loops 507 if (hasScroll) { 508 breakpointLocal = innerWidth; 509 root.classList.add("has-scroll"); 510 } else if ( 511 innerWidth > Math.min(breakpointLocal, breakpointMobile) 512 ) { 513 root.classList.remove("has-scroll"); 514 } 515 }); 516 }); 517 }); 518 observer.observe(root); 519 } 520 } 521 mobileTables(); 522 } 523 524 // ----------------------------- 525 // INTERACTIVE TABS (- Product) 526 // ----------------------------- 527 if ($(".interactive-tabs, .interactive-tabs-prod").length) { 528 function interactiveTabs() { 529 const MOBILE_TABS_BREAKPOINT = 1024; 530 531 document 532 .querySelectorAll(".interactive-tabs, .interactive-tabs-prod") 533 .forEach(($parent) => { 534 // initializing 535 const $tablist = $parent.querySelector('[role="tablist"]'); 536 const $$tabs = [...$tablist.querySelectorAll("button")]; 537 const $$tabpanels = [ 538 ...$parent.querySelectorAll('[role="tabpanel"]'), 539 ]; 540 const $firstTab = $$tabs[0]; 541 const $lastTab = $$tabs[$$tabs.length - 1]; 542 let $currentTab = $firstTab; 543 544 // mode for tabs or accordion depending on size, tabs is always horizontal 545 let isTabs = true; 546
547 setActiveTab($firstTab, false, false); 548 549 function getTabForToken(token) { 550 if (!token) { 551 return null; 552 } 553 554 const normalizedToken = token.trim(); 555 if (!normalizedToken) { 556 return null; 557 } 558 559 if (/^\d+$/.test(normalizedToken)) { 560 const tabIndex = Number.parseInt(normalizedToken, 10) - 1; 561 if (tabIndex >= 0 && tabIndex < $$tabs.length) { 562 return $$tabs[tabIndex]; 563 } 564 } 565 566 let matchingTab = $$tabs.find( 567 (tab) => 568 tab.id === normalizedToken || 569 tab.getAttribute("aria-controls") === normalizedToken, 570 ); 571 572 if (matchingTab) { 573 return matchingTab; 574 } 575 576 const matchingPanel = $$tabpanels.find( 577 (panel) => panel.id === normalizedToken, 578 ); 579 580 if (matchingPanel) { 581 matchingTab = $$tabs.find( 582 (tab) => 583 tab.getAttribute("aria-controls") === matchingPanel.id, 584 ); 585 } 586 587 return matchingTab || null; 588 } 589 590 function activateTabFromUrl() { 591 const url = new URL(window.location.href); 592 const urlTabToken = 593 url.searchParams.get("tab") || 594 url.searchParams.get("tabs") || 595 url.searchParams.get("panel"); 596 597 let tabToActivate = getTabForToken(urlTabToken); 598 const hashToken = decodeURIComponent(url.hash.replace(/^#/, "")); 599 let hashTargetInsidePanel = null; 600 601 if (!tabToActivate && hashToken) { 602 tabToActivate = getTabForToken(hashToken); 603 604 if (!tabToActivate) { 605 const hashTarget = document.getElementById(hashToken); 606 if (hashTarget) { 607 const hashTargetPanel = 608 hashTarget.closest('[role="tabpanel"]'); 609 if (hashTargetPanel && $parent.contains(hashTargetPanel)) { 610 tabToActivate = $$tabs.find( 611 (tab) => 612 tab.getAttribute("aria-controls") === 613 hashTargetPanel.id, 614 ); 615 hashTargetInsidePanel = hashTarget; 616 } 617 } 618 } 619 } 620 621 if (tabToActivate && tabToActivate !== $currentTab) {
622 setActiveTab(tabToActivate, false, false); 623 } 624 625 if (hashTargetInsidePanel) { 626 requestAnimationFrame(() => { 627 hashTargetInsidePanel.scrollIntoView({ 628 block: "start", 629 behavior: "auto", 630 }); 631 }); 632 } 633 } 634 635 // functions 636 637 function setMode() { 638 let containerStyle = getComputedStyle($parent); 639 640 let containerWidth = 641 $parent.getBoundingClientRect().width - 642 parseInt(containerStyle.getPropertyValue("padding-left")) - 643 parseInt(containerStyle.getPropertyValue("padding-right")); 644 645 const tabsRequiredWidth = $$tabs.reduce( 646 (totalWidth, tab) => totalWidth + tab.scrollWidth, 647 0, 648 ); 649 650 const isMobileViewport = 651 window.innerWidth <= MOBILE_TABS_BREAKPOINT; 652 653 // On mobile we always collapse tabs into the non-horizontal mode. 654 isTabs = !isMobileViewport && tabsRequiredWidth <= containerWidth; 655 656 if (!isTabs) { 657 $parent.classList.add("dropdown-mode"); 658 $parent.classList.add("accordion-mode"); 659 660 $$tabs.forEach((tab) => (tab.tabIndex = "0")); 661 handleTabsDropdown($currentTab); 662 $tablist.style.removeProperty("--tabs-sticky-top"); 663 } else { 664 $parent.classList.remove("dropdown-mode"); 665 $parent.classList.remove("accordion-mode"); 666 } 667 668 setStickyOffset(); 669 } 670 setMode(); 671 activateTabFromUrl(); 672 673 function setStickyOffset() { 674 if (!isTabs) { 675 return 0; 676 } 677 678 const $header = document.querySelector("header.region-header"); 679 let stickyTop = 0; 680 681 if ($header) { 682 const headerStyle = getComputedStyle($header); 683 if ( 684 headerStyle.position === "fixed" || 685 headerStyle.position === "sticky" 686 ) { 687 stickyTop = Math.max( 688 0, 689 Math.ceil($header.getBoundingClientRect().bottom), 690 ); 691 } 692 } 693 694 $tablist.style.setProperty("--tabs-sticky-top", `${stickyTop}px`); 695 return stickyTop; 696 } 697 698 function setContainerHeight() { 699 const $tabpanel = $parent.querySelector( 700 `#${$currentTab.getAttribute("aria-controls")}`, 701 ); 702 if (!$tabpanel) { 703 return; 704 } 705 706 // Recalculate static chrome height every time to avoid stale values 707 // when tab content changes after async/media render. 708 const parentRect = $parent.getBoundingClientRect(); 709 const panelRect = $tabpanel.getBoundingClientRect(); 710 const chromeHeight = Math.max( 711 0, 712 parentRect.height - panelRect.height, 713 ); 714 const panelHeight = Math.max( 715 panelRect.height, 716 $tabpanel.scrollHeight, 717 ); 718 719 $parent.style.setProperty( 720 "--height", 721 chromeHeight + panelHeight + "px", 722 ); 723 } 724 725 function scrollToTablistTop() { 726 if (!isTabs) { 727 return; 728 } 729 730 const stickyTop = setStickyOffset(); 731 const targetY = 732 $parent.getBoundingClientRect().top + 733 window.scrollY - 734 stickyTop - 735 12; 736 737 window.scrollTo({ 738 top: Math.max(0, targetY), 739 behavior: "smooth", 740 }); 741 } 742 743 function setActiveTab( 744 $activeTab, 745 willFocus = true, 746 shouldScroll = true, 747 ) { 748 $currentTab.setAttribute("aria-selected", "false"); 749 if (isTabs) { 750 $currentTab.tabIndex = "-1"; 751 } 752 753 $parent 754 .querySelector(`#${$currentTab.getAttribute("aria-controls")}`) 755 .classList.add("hidden"); 756 757 $currentTab = $activeTab; 758 if (willFocus) { 759 $currentTab.focus(); 760 } 761 $currentTab.setAttribute("aria-selected", "true"); 762 $currentTab.tabIndex = "0"; 763 $parent 764 .querySelector(`#${$currentTab.getAttribute("aria-controls")}`) 765 .classList.remove("hidden"); 766 setIndicator($currentTab); 767 setContainerHeight($currentTab); 768 requestAnimationFrame(() => setContainerHeight()); 769 770 // Some inner components render a bit later (images/carousel/tables). 771 // Re-check once more after transitions to prevent truncated content. 772 setTimeout(() => setContainerHeight(), 320); 773 774 if ( 775 $parent 776 .querySelector( 777 `#${$currentTab.getAttribute("aria-controls")}`, 778 ) 779 .querySelector(".text-formatted table") 780 ) { 781 mobileTables(); 782 } 783 784 if (!isTabs) { 785 handleTabsDropdown($currentTab); 786 hideTablist(); 787 } 788 789 if (shouldScroll) { 790 scrollToTablistTop(); 791 } 792 } 793 794 function setActiveToNextTab() { 795 if ($currentTab === $lastTab) {
796 setActiveTab($firstTab); 797 } else { 798 let i = $$tabs.indexOf($currentTab); 799 setActiveTab($$tabs[i + 1]); 800 } 801 } 802 803 function setActiveToPrevTab() { 804 if ($currentTab === $firstTab) { 805 setActiveTab($lastTab); 806 } else { 807 let i = $$tabs.indexOf($currentTab); 808 setActiveTab($$tabs[i - 1]); 809 } 810 } 811 812 function setIndicator() { 813 $tablist.style.setProperty( 814 "--indicator-x", 815 $currentTab.getBoundingClientRect().x - 816 $tablist.getBoundingClientRect().x + 817 "px", 818 ); 819 $tablist.style.setProperty( 820 "--indicator-width", 821 $currentTab.getBoundingClientRect().width + "px", 822 ); 823 } 824 825 const handleResize = debounce(() => { 826 setMode(); 827 setStickyOffset(); 828 setIndicator(); 829 setContainerHeight(); 830 }); 831 832 function handleClick() {
833 setActiveTab(this); 834 } 835 836 function handleKeydown(e) { 837 let flag = false; 838 839 switch (e.key) { 840 case "ArrowLeft": 841 setActiveToPrevTab(); 842 flag = true; 843 break; 844 845 case "ArrowRight": 846 setActiveToNextTab(); 847 flag = true; 848 break; 849 850 case "Home": 851 setActiveTab($firstTab); 852 flag = true; 853 break; 854 855 case "End": 856 setActiveTab($lastTab); 857 flag = true; 858 break; 859 860 default: 861 break; 862 } 863 864 if (flag) { 865 e.stopPropagation(); 866 e.preventDefault(); 867 } 868 } 869 870 function debounce(func, timeout = 300) { 871 let timer; 872 return (...args) => { 873 clearTimeout(timer); 874 timer = setTimeout(() => { 875 func.apply(this, args); 876 }, timeout); 877 }; 878 } 879 880 // event listeners 881 window.addEventListener("load", handleResize); //resize after images loaded 882 window.addEventListener("resize", handleResize); 883 window.addEventListener("hashchange", activateTabFromUrl); 884 885 $$tabs.forEach(($tab) => { 886 $tab.addEventListener("click", handleClick); 887 $tab.addEventListener("keydown", handleKeydown); 888 }); 889 890 function handleTabsDropdown(tab) { 891 if (isTabs) { 892 return; 893 } else { 894 const tabsToggleBtn = $parent.querySelector(".dropdown-toggle"); 895 tabsToggleBtn.innerText = tab.innerText; 896 897 tabsToggleBtn.addEventListener("click", hideTablist); 898 } 899 } 900 901 function hideTablist() { 902 $tablist.classList.toggle("dropdown-visible"); 903 } 904 905 let hasAccordion = $parent.querySelectorAll(".accordion-list"); 906 if (hasAccordion.length) { 907 // Resize observer for height changes in panel 908 const tabsResizeObserver = new ResizeObserver((entries) => { 909 setContainerHeight(); 910 }); 911 hasAccordion.forEach((accordion) => { 912 accordion 913 .querySelectorAll('[role="accordionpanel"]') 914 .forEach((x) => tabsResizeObserver.observe(x)); 915 }); 916 } 917 918 // Keep active panel height in sync when tab content changes dynamically. 919 if (window.ResizeObserver) { 920 const panelResizeObserver = new ResizeObserver(() => { 921 setContainerHeight(); 922 }); 923 924 $$tabpanels.forEach((panel) => 925 panelResizeObserver.observe(panel), 926 ); 927 } 928 929 // Fallback for late-loading media that can change panel height. 930 $$tabpanels.forEach((panel) => { 931 panel 932 .querySelectorAll("img, iframe, video") 933 .forEach((mediaElement) => { 934 mediaElement.addEventListener("load", setContainerHeight); 935 mediaElement.addEventListener( 936 "loadedmetadata", 937 setContainerHeight, 938 ); 939 }); 940 }); 941 }); 942 } 943 interactiveTabs(); 944 } 945 946 // ----------------------------- 947 // ACCORDION 948 // ----------------------------- 949 if ($(".accordion-list").length) { 950 function accordion() { 951 document.querySelectorAll(".accordion-list").forEach((parent) => { 952 // initializing 953 const detailslist = parent.querySelector(".details-wrapper"); 954 const summaries = [...detailslist.querySelectorAll("button.summary")]; 955 956 let currentPanel = summaries[0]; 957 let expanded; 958
959 setActivePanel(currentPanel); 960 961 function setActivePanel(activePanel) { 962 currentPanel = activePanel; 963 964 expanded = 965 currentPanel.getAttribute("aria-expanded") == "true" 966 ? true 967 : false; 968 expanded = !expanded; 969 970 // toggle current panel visibility 971 currentPanel.setAttribute("aria-expanded", expanded); 972 parent 973 .querySelector(`#${currentPanel.getAttribute("aria-controls")}`) 974 .setAttribute("aria-hidden", !expanded); 975 976 if ( 977 parent 978 .querySelector(`#${currentPanel.getAttribute("aria-controls")}`) 979 .querySelector(".text-formatted table") 980 ) { 981 setTimeout(() => { 982 mobileTables(); 983 }, 400); 984 } 985 986 setPanelHeight(currentPanel, expanded); 987 } 988 989 const handleResize = debounce(() => { 990 setPanelHeight(currentPanel, expanded); 991 }); 992 993 function handleClick() {
994 setActivePanel(this); 995 } 996 997 function debounce(func, timeout = 300) { 998 let timer; 999 return (...args) => { 1000 clearTimeout(timer); 1001 timer = setTimeout(() => { 1002 func.apply(this, args); 1003 }, timeout); 1004 }; 1005 } 1006 1007 // event listeners 1008 window.addEventListener("load", handleResize); //resize after images loaded 1009 window.addEventListener("resize", handleResize); 1010 1011 summaries.forEach((summary) => { 1012 summary.addEventListener("click", handleClick); 1013 }); 1014 }); 1015 1016 function setPanelHeight(currentPanel, expanded) { 1017 const content = currentPanel.parentElement.querySelector( 1018 `#${currentPanel.getAttribute("aria-controls")}`, 1019 ); 1020 1021 let bh = currentPanel.getBoundingClientRect().height; 1022 1023 if (bh <= 0) { 1024 return; 1025 } 1026 1027 const threshold = 1.25; 1028 let ch = expanded 1029 ? content.getBoundingClientRect().height * threshold 1030 : 0; 1031 1032 let panelHeight = 1033 currentPanel.getBoundingClientRect().height + ch + "px"; 1034 1035 currentPanel.parentElement.style.setProperty("--height", panelHeight); 1036 } 1037 } 1038 accordion(); 1039 } 1040 1041 // ------------------- 1042 // VIDEO BANNERS 1043 // ------------------- 1044 if ($("[data-is-video-banner]").length) { 1045 function videoBanners() { 1046 let [...videoBanners] = document.querySelectorAll( 1047 "[data-is-video-banner]", 1048 ); 1049 1050 videoBanners.forEach((banner) => { 1051 createVideoBannerObserver(banner); 1052 }); 1053 1054 function createVideoBannerObserver(banner) { 1055 let options = { threshold: 0.25 }; 1056 const videoBannerObserver = new IntersectionObserver( 1057 handleVideoBannerObserver, 1058 options, 1059 ); 1060 videoBannerObserver.observe(banner); 1061 } 1062 1063 function handleVideoBannerObserver(entries, options) { 1064 entries.forEach((entry) => { 1065 let playState = null; 1066 const video = entry.target.querySelector("video"); 1067 if (!entry.isIntersecting) { 1068 video.pause(); 1069 playState = false; 1070 } else { 1071 video.play(); 1072 playState = true; 1073 } 1074 }); 1075 } 1076 } 1077 videoBanners(); 1078 } 1079 1080 // ------------------- 1081 // STORIES SEARCH + FILTER 1082 // ------------------- 1083 const createStory = function (wrapper) { 1084 let filters = new Set(); 1085 let query = ""; 1086 1087 function liveSearch() { 1088 wrapper.querySelectorAll(".story-item").forEach((story) => { 1089 // at least one filter must be on for filtering to happen 1090 if (filters.size && !filters.has(story.dataset.term)) { 1091 return (story.dataset.visible = false); 1092 } 1093 1094 // search multiple elements 1095 const content = [".term", ".type", ".title", ".copy"] 1096 .map((q) => story.querySelector(q).innerText) 1097 .join(" || ") 1098 .toLowerCase(); 1099 1100 // look for multiple words, false as soon as one query is missing 1101 for (const q of query.toLowerCase().split(" ")) { 1102 if (!content.includes(q)) { 1103 return (story.dataset.visible = false); 1104 } 1105 } 1106 1107 return (story.dataset.visible = true); 1108 }); 1109 } 1110 1111 function toggleFilter(el) { 1112 // reset filter on other elements 1113 let siblings = wrapper.querySelectorAll(".story-tag-toggle"); 1114 Array.from(siblings) 1115 .filter((x) => x != el) 1116 .forEach((sibling) => { 1117 sibling.dataset.toggled = "false"; 1118 sibling.dataset.color = "white"; 1119 filters.delete(sibling.dataset.tag); 1120 }); 1121 1122 // filter on single element 1123 if (el.dataset.toggled == "true") { 1124 // turning on to off 1125 el.dataset.toggled = "false"; 1126 el.dataset.color = "white"; 1127 filters.delete(el.dataset.tag); 1128 } else { 1129 // turning off to on 1130 el.dataset.toggled = "true"; 1131 el.dataset.color = "green"; 1132 filters.add(el.dataset.tag); 1133 } 1134 liveSearch(); 1135 } 1136 1137 wrapper 1138 .querySelectorAll(".story-tag-toggle") 1139 .forEach((el) => el.addEventListener("click", () => toggleFilter(el))); 1140 1141 // on input change; would need a delay? 1142 wrapper.querySelector(".search").addEventListener("input", (e) => { 1143 e.preventDefault(); 1144 query = e.target.value; 1145 liveSearch(); 1146 }); 1147 1148 // on submit 1149 wrapper.querySelector(".search").addEventListener("submit", (e) => { 1150 e.preventDefault(); 1151 query = e.target.q.value; 1152 liveSearch(); 1153 }); 1154 }; 1155 1156 document 1157 .querySelectorAll(".stories") 1158 .forEach((wrapper) => createStory(wrapper)); 1159 1160 // ------------------- 1161 // NEWS HIGHLIGHTS 1162 // ------------------- 1163 if ($(".news-highlights-collection").length) { 1164 function newsHighlights() { 1165 let [...newsHighlightsCol] = document.querySelectorAll( 1166 ".news-highlights-collection", 1167 ); 1168 1169 newsHighlightsCol.forEach((col) => { 1170 const btn = col.querySelector(".expand-btn"); 1171 btn.addEventListener("click", () => { 1172 col.classList.add("expanded"); 1173 }); 1174 }); 1175 } 1176 newsHighlights(); 1177 } 1178 1179 // ------------------- 1180 // Pardot form 1181 // ------------------- 1182 // Extract everything after the matched language code 1183 function getTextAfterLanguage(url) { 1184 //const regex = /\/[a-z]{2}-[a-z]{2}\//i;
1184 // This matches patterns like "/en-us/", "/fr-fr/", etc. 1185 //Specific pattern for 'en-us' 1186 const enUsPattern = /\/en-us\//i; // This matches exactly "/en-us/" 1187 const match = url.match(enUsPattern); 1188 if (match) { 1189 return url.substring(match.index + match[0].length); 1190 } 1191 return ""; 1192 } 1193 // Is a product modality page 1194 function isValidPath(url) { 1195 const validProducts = [ 1196 "x-ray-ct", 1197 "mri", 1198 "ultrasound", 1199 "nuclear-medicine", 1200 "cardiology", 1201 ]; 1202 const extractedText = getTextAfterLanguage(url); 1203 if (validProducts.includes(extractedText)) { 1204 return extractedText; // Return the extracted text if valid 1205 } 1206 1207 return false; // Return false if it's not valid 1208 } 1209 // Autofill slug on Pardot form. 1210 function addProductNameToForm() { 1211 if ($("input[name='product_url']")) { 1212 // var hash = location.hash.replace('#', ''); 1213 var locPathname = window.location.pathname; 1214 1215 const urlParams = new URLSearchParams(window.location.search); 1216 1217 if (urlParams.has("product")) { 1218 const product = urlParams.get("product"); 1219 $("input[name='product_url']").val(product); 1220 //console.log('product_url',hash); 1221 } else { 1222 // if no hash, try to check for product 1223 let productName = isValidPath(locPathname); 1224 if (productName !== false) { 1225 $("input[name='product_url']").val(productName); 1226 //console.log('product_url',productName); 1227 } else { 1228 $("input[name='product_url']").val("citraclear"); 1229 //console.log('product_url',"citraclear"); 1230 } 1231 } 1232 } 1233 } 1234 addProductNameToForm(); 1235 1236 // ------------------- 1237 // WINDOW LOCATION ANCHOR PRESENT 1238 // ------------------- 1239 window.addEventListener("hashchange", function () { 1240 addProductNameToForm(); 1241 }); 1242 1243 // ------------------- 1244 // WINDOW LOCATION ANCHOR PRESENT 1245 // ------------------- 1246 if (window.location.hash) { 1247 let hash = window.location.hash; 1248 let scrollTop = window.innerWidth < 1023 ? 100 : 147; 1249 $("html, body").animate( 1250 { 1251 scrollTop: $(hash).offset().top - scrollTop, 1252 }, 1253 500, 1254 ); 1255 } 1256 1257 if ($("#edit-discard-changes-onclick").length) { 1258 $("#edit-discard-changes-onclick").on("click", function (e) { 1259 $("#edit-discard-changes").click(); 1260 }); 1261 } 1262 }); 1263 1264 // ------------------- 1265 // Main nav menu 1266 // ------------------- 1267 if (window.innerWidth > 1024) { 1268 // initMainNavMenu(); 1269 } 1270 initMainNavMenu(); 1271 1272 if (window.innerWidth < 1023) { 1273 document.querySelector("html").style.scrollBehavior = "auto"; 1274 mobileScrollToAnchor(); 1275 } 1276 1277 setTimeout(() => { 1278 handleHamburgerMenu(); 1279 }, 800); 1280 initComparisonSlider(); 1281 initZoomAnimation(); 1282 1283 // SEARCH BAR IN MAIN MENU 1284 const searchButton = document.querySelector(".search-menu form button"); 1285 1286 searchButton.addEventListener( 1287 "click", 1288 function (e) { 1289 e.preventDefault(); 1290 const previousElement = this.previousElementSibling; 1291 if (previousElement) { 1292 previousElement.classList.add("visible"); 1293 setTimeout(function () { 1294 previousElement.focus(); 1295 }, 500); 1296 } 1297 }, 1298 { once: true }, 1299 ); 1300 1301 // ------------------- 1302 // Lazy loading videos 1303 // ------------------- 1304 var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy")); 1305 1306 if ("IntersectionObserver" in window) { 1307 var lazyVideoObserver = new IntersectionObserver(function ( 1308 entries, 1309 observer, 1310 ) { 1311 entries.forEach(function (video) { 1312 if (video.isIntersecting) { 1313 for (var source in video.target.children) { 1314 var videoSource = video.target.children[source]; 1315 if ( 1316 typeof videoSource.tagName === "string" && 1317 videoSource.tagName === "SOURCE" 1318 ) { 1319 videoSource.src = videoSource.dataset.src; 1320 } 1321 } 1322 1323 video.target.load(); 1324 video.target.classList.remove("lazy"); 1325 lazyVideoObserver.unobserve(video.target); 1326 } 1327 }); 1328 }); 1329 1330 lazyVideos.forEach(function (lazyVideo) { 1331 lazyVideoObserver.observe(lazyVideo); 1332 }); 1333 } 1334})(jQuery); 1335 1336// ------------------- 1337// COMPARISON SLIDER 1338// ------------------- 1339function initComparisonSlider() { 1340 const sliderContainer = document.querySelector( 1341 ".comparison-slider-container", 1342 ); 1343 const sliderContainerRangeBtn = document.querySelector( 1344 ".comparison-slider-container > .slider-range", 1345 ); 1346 if (sliderContainer) { 1347 sliderContainerRangeBtn.addEventListener("input", (e) => { 1348 sliderContainer.style.setProperty("--position", `${e.target.value}%`); 1349 }); 1350 } 1351} 1352 1353// âââââââââââââââââââââââââââââââââ
1354// Breakpoint Handler 1355// âââââââââââââââââââââââââââââââââ 1356// Breakpoints (no overlaps) 1357const breakpoints = [ 1358 { 1359 key: "mobile", 1360 value: "(min-width: 0px) and (max-width: 1023px)", 1361 }, 1362 { 1363 key: "desktop", 1364 value: "(min-width: 1024px)", 1365 }, 1366]; 1367 1368let prevBreakpoint; 1369function breakpointHandler() { 1370 // find current breakpoint 1371 const breakpoint = breakpoints 1372 .filter((bp) => matchMedia(bp.value).matches) 1373 .pop().key; 1374 1375 // exit if breakpoint is unchanged 1376 if (breakpoint === prevBreakpoint) return; 1377 prevBreakpoint = breakpoint; 1378 1379 // call functions 1380 switch (breakpoint) { 1381 case "mobile": { 1382 //console.log('dispatch m'); 1383 dispatchEvent(new CustomEvent("mediaMobile")); 1384 break; 1385 } 1386 case "desktop": { 1387 //console.log('dispatch'); 1388 dispatchEvent(new CustomEvent("mediaDesktop")); 1389 break; 1390 } 1391 } 1392} 1393 1394// function setMenuHeight(mobileView) { 1395// if (!mobileView) { 1396// initMainNavMenu(); 1397// } 1398// } 1399 1400function initMainNavMenu() { 1401 jQuery(".first-level-menu .megamenu") 1402 .on("mouseenter", function () { 1403 if (window.innerWidth > 1024) { 1404 jQuery(".first-level-menu .megamenu").removeClass("clicked"); 1405 jQuery(".first-level-menu .megamenu > button").removeClass("active"); 1406 jQuery(this).addClass("clicked"); 1407 } 1408 }) 1409 .on("mouseleave", function () { 1410 if (window.innerWidth > 1024) { 1411 jQuery(this).removeClass("clicked"); 1412 } 1413 }); 1414 jQuery(".second-level .has-sub-level") 1415 .on("mouseenter", function () { 1416 if (window.innerWidth > 1024) { 1417 jQuery(".second-level .has-sub-level").removeClass("clicked"); 1418 jQuery(this).addClass("clicked"); 1419 } 1420 }) 1421 .on("mouseleave", function () { 1422 if (window.innerWidth > 1024) { 1423 jQuery(this).removeClass("clicked"); 1424 } 1425 }); 1426 jQuery("body").on("click", function (e) { 1427 if (!jQuery(e.target).closest(".megamenu").length) { 1428 jQuery(".first-level-menu .megamenu").removeClass("clicked"); 1429 jQuery(".second-level .has-sub-level").removeClass("clicked"); 1430 } 1431 }); 1432} 1433 1434// ------------------- 1435// HAMBURGER MENU 1436// ------------------- 1437function handleHamburgerMenu() { 1438 // elements 1439 const header = document.querySelector("header.region-header"); 1440 const mobileMenuContainer = header.querySelector(".mobile-menu"); 1441 const mobileMenuButton = mobileMenuContainer.querySelector( 1442 ".hamburger-menu-button", 1443 ); 1444 // -- elements in first level menu 1445 const firstLevelMenu = header.querySelector( 1446 ".header-inner .block-main-menu .first-level-menu", 1447 ); 1448 const productsMegamenu = firstLevelMenu.querySelector( 1449 ".menu-items .products-megamenu button.link", 1450 ); 1451 let [...goBackButtons] = firstLevelMenu.querySelectorAll( 1452 ".menu-items .megamenu .second-level-menu > .go-back-burgermenu", 1453 ); 1454 let [...goToSecondLevelButtons] = firstLevelMenu.querySelectorAll( 1455 ".menu-items .megamenu .third-level .go-back-burgermenu", 1456 ); 1457 let [...firstLevelLinks] = firstLevelMenu.querySelectorAll( 1458 ".header-inner .block-main-menu .first-level-menu .menu-items .megamenu>.link", 1459 ); 1460 1461 let [...secondLevelLinks] = firstLevelMenu.querySelectorAll( 1462 ".header-inner .block-main-menu .second-level-menu .menu-items .has-sub-level>.link", 1463 ); 1464 let [...menuMainLinks] = firstLevelMenu.querySelectorAll( 1465 ".header-inner .block-main-menu .first-level-menu>.menu-items>.menu-item>.link", 1466 ); 1467 let [...closeMegaMenuBtnDesktop] = header.querySelectorAll( 1468 ".second-level-menu .close-megamenu", 1469 ); 1470 1471 function toggleHeaderExpansion(externalTrigger) { 1472 header.classList.toggle("expanded"); 1473 openMegaMenuMobile = !openMegaMenuMobile; 1474 1475 if (openMenuItem && !externalTrigger) { 1476 collapseMenu(); 1477 } 1478 if (externalTrigger) { 1479 openMenuItem = productsMegamenu; 1480 expandMenu(); 1481 } 1482 } 1483 1484 // ------------------- 1485 // Event listeners 1486 // ------------------- 1487 if (mobileMenuButton) { 1488 mobileMenuButton.addEventListener("click", () => { 1489 toggleHeaderExpansion(false); 1490 }); 1491 } 1492 // Keyup event listeners for tab key on main menu links 1493 menuMainLinks.forEach((el) => { 1494 el.addEventListener("keyup", (e) => { 1495 if (e.key === "Tab" && openMenuItem) { 1496 collapseMenu(); 1497 } 1498 }); 1499 }); 1500 // Keyup event listeners for escape key 1501 header.addEventListener("keyup", (e) => { 1502 if (e.key === "Escape") { 1503 if (openMenuItem) { 1504 collapseMenu(); 1505 } else { 1506 mobileMenuButton.click(); 1507 } 1508 } 1509 }); 1510 // Go back to first level menu on mobile 1511 goBackButtons.forEach((el) => { 1512 el.addEventListener("click", () => { 1513 collapseMenu(); 1514 }); 1515 }); 1516 // Go back to second level menu on mobile 1517 goToSecondLevelButtons.forEach((el) => { 1518 el.addEventListener("click", () => { 1519 backToSecond(); 1520 }); 1521 }); 1522 // Close desktop mega menu on click
1523 closeMegaMenuBtnDesktop.forEach((btn) => { 1524 btn.addEventListener("click", collapseMenu); 1525 }); 1526 1527 // Trigger first level button events 1528 firstLevelLinks.forEach((el) => { 1529 // listen for clicks on first level buttons 1530 el.addEventListener("click", (e) => { 1531 if (openMenuItem) collapseMenu(); 1532 openMenuItem = e.target; 1533 expandMenu(); 1534 }); 1535 // listen for mouseenter on first level buttons 1536 el.addEventListener("mouseenter", (e) => { 1537 if (!mobileView && openMenuItem) { 1538 if (openMenuItem !== e.target) collapseMenu(); 1539 } 1540 }); 1541 }); 1542 // Trigger second level button events 1543 secondLevelLinks.forEach((el) => { 1544 // listen for clicks on first level buttons 1545 el.addEventListener("click", (e) => { 1546 //if (openMenuItem) collapseMenu(); 1547 //openMenuItem = e.target; 1548 expandThirdLevelMenu(e.target); 1549 }); 1550 }); 1551 // collapse submenu on mouseleave if submenu is open 1552 header 1553 .querySelector(".block-main-menu") 1554 .addEventListener("mouseleave", (e) => { 1555 if (!mobileView && openMenuItem) collapseMenu(); 1556 }); 1557 // collapse submenu on click or tab outside 1558 ["focusin", "click"].forEach((event) => { 1559 addEventListener(event, (e) => { 1560 if (!openMenuItem && !openMegaMenuMobile) return; 1561 if ( 1562 e.target.classList.contains("show-products-menu") || 1563 e.target.closest(".region-header") 1564 ) 1565 return; 1566 if (openMenuItem) return collapseMenu(); 1567 if (openMegaMenuMobile) return toggleHeaderExpansion(false); 1568 }); 1569 }); 1570} 1571 1572// --------------------- // 1573// Toggle submenu states // 1574// --------------------- // 1575let openMenuItem; 1576let openMegaMenuMobile = false; 1577 1578const header = document.querySelector("header.region-header"); 1579 1580const setAriaAttributes = (state, item, menu) => { 1581 item.setAttribute("aria-expanded", `${state}`); 1582 menu.setAttribute("aria-hidden", `${!state}`); 1583 return; 1584}; 1585 1586function expandMenu() { 1587 const menu = document.querySelector( 1588 `#${openMenuItem.getAttribute("aria-controls")}`, 1589 ); 1590 setAriaAttributes(true, openMenuItem, menu); 1591 1592 return setTimeout(() => { 1593 menu.classList.add("expanded"); 1594 }, 100); 1595} 1596function expandThirdLevelMenu(el) { 1597 const thirdLevelMenu = document.querySelector( 1598 `#${el.getAttribute("aria-controls")}`, 1599 ); 1600 1601 return setTimeout(() => { 1602 thirdLevelMenu.classList.add("expanded"); 1603 }, 100); 1604} 1605 1606function collapseMenu() { 1607 if (!openMenuItem) return; 1608 1609 let item = openMenuItem; 1610 1611 const menu = document.querySelector(`#${item.getAttribute("aria-controls")}`); 1612 menu.classList.remove("expanded"); 1613 1614 const time = mobileView ? 400 : 0; 1615 1616 setTimeout(() => { 1617 setAriaAttributes(false, item, menu); 1618 }, time); 1619 1620 return (openMenuItem = null); 1621} 1622 1623function backToSecond() { 1624 const menu = document.querySelector(`.menu-items.third-level.expanded`); 1625 menu.classList.remove("expanded"); 1626} 1627 1628// ---------------------------------------- 1629// Handle click activation of products menu 1630// ---------------------------------------- 1631let [...anchortags] = document.querySelectorAll(".layout .show-products-menu"); 1632if (anchortags.length) { 1633 const productsMegamenu = document.querySelector( 1634 ".header-inner .menu-items .products-megamenu button.link", 1635 );
1636 anchortags.forEach((a) => { 1637 a.addEventListener("click", (event) => { 1638 mobileView && header.classList.add("expanded"); 1639 productsMegamenu.focus(); 1640 openMenuItem = productsMegamenu; 1641 expandMenu(); 1642 event.preventDefault; 1643 }); 1644 }); 1645} 1646 1647// ------------------- 1648// Zoom In/Out animation 1649// ------------------- 1650function initZoomAnimation() { 1651 let [...animatedImages] = document.querySelectorAll(".zoom-animation img"); 1652 1653 if (animatedImages.length) { 1654 ScrollTrigger.defaults({ 1655 scroller: "body", 1656 }); 1657 1658 animatedImages.forEach((animatedImage) => { 1659 gsap.to(animatedImage, { 1660 scrollTrigger: { 1661 trigger: animatedImage, 1662 start: "20% bottom", 1663 end: "bottom top", 1664 scrub: true, 1665 }, 1666 scale: 1.3, 1667 ease: "none", 1668 }); 1669 }); 1670 } 1671} 1672 1673// ------------------- 1674// Mobile Scroll to Anchor from menu item click 1675// ------------------- 1676function mobileScrollToAnchor() { 1677 const navLinks = document.querySelectorAll(".menu-item a.link"); 1678 const mobileMenu = document.querySelector("header"); 1679 1680 if (navLinks) { 1681 navLinks.forEach((anchor) => { 1682 anchor.addEventListener("click", function () { 1683 window.addEventListener("hashchange", function () { 1684 const id = window.location.hash; 1685 const yOffset = 200; 1686 const element = document.querySelector(id); 1687 const y = 1688 element.getBoundingClientRect().top + window.pageYOffset - yOffset; 1689 1690 window.scrollTo({ top: y, behavior: "smooth" }); 1691 1692 setTimeout(function () { 1693 mobileMenu.classList.remove("expanded"); 1694 }, 500); 1695 }); 1696 }); 1697 }); 1698 } 1699} 1700 1701// ------------------- 1702// Inquiry form 1703// ------------------- 1704// Function to fetch and extract the locationCode 1705async function fetchLocationCode() { 1706 let ret = drupalSettings.locationCode || ""; 1707 if (ret) { 1708 return ret; 1709 } 1710 try { 1711 if ( 1712 drupalSettings.exception === 403 || 1713 drupalSettings.exception === 404 || 1714 drupalSettings.path.currentPathIsAdmin 1715 ) { 1716 return; 1717 } 1718 const response = await fetch("/api/location_get", { 1719 method: "GET", 1720 headers: { 1721 "Content-Type": "application/json", 1722 }, 1723 }); 1724 1725 if (!response.ok) { 1726 throw new Error(`HTTP error! Status: ${response.status}`); 1727 } 1728 const data = await response.json(); 1729 drupalSettings.locationCode = data.data?.locationCode || ""; 1730 return drupalSettings.locationCode; 1731 } catch (error) { 1732 console.error("Error fetching location code:", error); 1733 return ""; 1734 } 1735} 1736 1737async function fetchLocationCodeByUrl() { 1738 if ( 1739 drupalSettings.exception === 403 || 1740 drupalSettings.exception === 404 || 1741 drupalSettings.path.currentPathIsAdmin 1742 ) { 1743 return ""; 1744 } 1745 let ret = drupalSettings.locationCodeByUrl || ""; 1746 if (ret) { 1747 return ret; 1748 } 1749 1750 try { 1751 let url = window.location.origin + "/"; 1752 if (drupalSettings.path.pathPrefix !== "") { 1753 url = url + drupalSettings.path.pathPrefix; 1754 } 1755 const response = await fetch(url + "api/location_get_by_language", { 1756 method: "GET", 1757 headers: { 1758 "Content-Type": "application/json", 1759 }, 1760 }); 1761 1762 if (!response.ok) { 1763 throw new Error(`HTTP error! Status: ${response.status}`); 1764 } 1765 const data = await response.json(); 1766 drupalSettings.locationCodeByUrl = data.data?.locationCode || ""; 1767 return drupalSettings.locationCodeByUrl; 1768 } catch (error) { 1769 console.error("Error fetching location code:", error); 1770 return ""; 1771 } 1772} 1773 1774if (document.querySelector(".block-inquiry-form")) { 1775 const countryDropdownBtn = document.querySelectorAll(".dropdown-wrapper-btn"); 1776 1777 function initProductDropdown(ref) { 1778 // Clear the label in the product dropdown each time the country dropdown is changed 1779 const refBtn = ref.querySelector('[data-comp="ref-button"]'); 1780 refBtn.textContent = "..."; 1781 refBtn.removeAttribute("lang"); 1782 1783 return disableNextBtn(ref); 1784 } 1785 1786 function disableNextBtn(item) { 1787 // Disable the "Next" button until a new product selection is made 1788 const nextButtonWrapper = item.querySelector(".button-wrapper"); 1789 const nextButtonLinks = nextButtonWrapper.querySelector("a"); 1790 1791 nextButtonWrapper.classList.add("disabled"); 1792 1793 if (nextButtonLinks) { 1794 nextButtonLinks.removeAttribute("href"); // Clear the href 1795 delete nextButtonLinks.dataset.productName; 1796 nextButtonLinks.style.pointerEvents = "none"; // Disable click 1797 } 1798 } 1799 1800 function updateDropdownPosition() { 1801 const dropdownMenus = document.querySelectorAll( 1802 ".dropdown-wrapper-content", 1803 );
1804 dropdownMenus.forEach(function (menu) { 1805 const visibleItems = Array.from(menu.querySelectorAll("li")).filter( 1806 (li) => li.style.display !== "none", 1807 ); 1808 // Add or remove "bottom-pos" based on the number of visible items 1809 if (visibleItems.length > 4) { 1810 menu.classList.add("bottom-pos"); 1811 } else { 1812 menu.classList.remove("bottom-pos"); 1813 } 1814 }); 1815 } 1816 1817 function enableRefChooser(ref) { 1818 ref.classList.remove("disabled"); // Enable interaction 1819 ref.querySelector('[data-comp="ref-button"]').classList.remove("disabled"); 1820 } 1821 1822 function filterRefDropdown(selectedLang, refWrapper) { 1823 // Function to filter product dropdown items based on selected language 1824 enableRefChooser(refWrapper); 1825 const dropdownItems = refWrapper.querySelectorAll( 1826 ".dropdown-wrapper-content li", 1827 ); 1828 1829 let hasMatchingItems = false; 1830 1831 // Show/hide items based on lang attribute 1832 dropdownItems.forEach(function (item) { 1833 const link = item.querySelector("a"); 1834 1835 if (link && link.getAttribute("lang") === selectedLang) { 1836 item.style.display = ""; 1837 hasMatchingItems = true; 1838 } else { 1839 item.style.display = "none"; 1840 } 1841 }); 1842 1843 // If no items match, show an "Empty" option 1844 if (!hasMatchingItems) { 1845 refWrapper 1846 .querySelectorAll(".dropdown-wrapper-content .empty-item") 1847 .forEach(function (emptyItem) { 1848 emptyItem.parentElement.style.display = "none"; 1849 }); 1850 1851 refWrapper 1852 .querySelectorAll(".dropdown-wrapper-content .empty-item") 1853 .forEach(function (emptyItem) { 1854 emptyItem.parentElement.style.display = ""; 1855 }); 1856 1857 // Disable the "Next" button until a new product selection is made 1858 disableNextBtn(refWrapper); 1859 } 1860 } 1861 1862 function closeAllDropdowns() { 1863 document 1864 .querySelectorAll(".dropdown-wrapper-toggle.selected") 1865 .forEach(function (toggle) { 1866 toggle.classList.remove("selected"); 1867 }); 1868 } 1869 1870 // REVIEW: Add a document click listener to close dropdowns if clicked outside 1871 document.addEventListener("click", function (event) { 1872 let activeDropdowns = document.querySelectorAll( 1873 ".dropdown-wrapper-toggle.selected", 1874 ); 1875 if (activeDropdowns && activeDropdowns.length == 0) return; 1876 1877 const clickedInsideDropdown = event.target.closest(".dropdown-wrapper"); 1878 1879 if (!clickedInsideDropdown) { 1880 closeAllDropdowns(); 1881 } 1882 }); 1883 1884 // Function to preset the language 1885 async function presetLanguage() { 1886 try { 1887 // Fetch the locationCode from your API 1888 const fetchedLocationCode = await fetchLocationCodeByUrl(); 1889 1890 const dropdownWrapper = document.querySelectorAll( 1891 ".country-selector-wrapper > .dropdown-wrapper", 1892 ); 1893 1894 dropdownWrapper.forEach(function (item) { 1895 const dropdownBtn = item.querySelector(".dropdown-wrapper-btn"); 1896 const dropdownContent = item.querySelector(".dropdown-wrapper-content"); 1897 1898 // Find the corresponding <a> element in the dropdown 1899 const presetItem = Array.from( 1900 dropdownContent.querySelectorAll("li > a"), 1901 ).find((a) => a.getAttribute("lang") === fetchedLocationCode); 1902 1903 if (presetItem) { 1904 const selectedUrl = presetItem.getAttribute("href"); 1905 // Set the dropdown button's text and lang attribute 1906 dropdownBtn.textContent = presetItem.textContent.trim(); 1907 dropdownBtn.setAttribute("lang", fetchedLocationCode); 1908 1909 const ref = dropdownContent.getAttribute("data-ref-comp"); 1910 const refWrapper = document.querySelector(`[data-comp="${ref}"]`); 1911 const refDropdown = dropdownContent.getAttribute("data-comp"); 1912 1913 if (!refWrapper) { 1914 return; 1915 } 1916 1917 if (refDropdown == "language-dropdown") { 1918 initProductDropdown(refWrapper); 1919 updateDropdownPosition(); 1920 filterRefDropdown(fetchedLocationCode, refWrapper); 1921 } 1922 if (refDropdown == "secondary-dropdown") { 1923 // Update the "Next" button with the preset URL 1924 let nextButtonWrapperPharma = refWrapper.querySelector( 1925 ".button-wrapper.next", 1926 ); 1927 let nextButton = nextButtonWrapperPharma.querySelector("a"); 1928 1929 if (selectedUrl !== "#") { 1930 nextButton.setAttribute("href", selectedUrl); 1931 nextButton.style.pointerEvents = "auto"; 1932 nextButtonWrapperPharma.classList.remove("disabled"); 1933 } else { 1934 nextButtonWrapperPharma.classList.add("disabled"); 1935 nextButton.setAttribute("href", "#"); 1936 nextButton.style.pointerEvents = "none"; 1937 } 1938 } 1939 } 1940 }); 1941 } catch (error) { 1942 console.error("Error presetting language:", error); 1943 } 1944 } 1945 1946 // Initialize the dropdown preset and attach event listeners 1947 function initializeDropdown() { 1948 if (countryDropdownBtn.length === 0) { 1949 return; 1950 } 1951 presetLanguage(); // Preset the language
1952 countryDropdownBtn.forEach(function (item) { 1953 // foreach country selection dropdown 1954 item.addEventListener("click", function (e) { 1955 // If the item is disabled, ignore the click 1956 if (item.classList.contains("disabled")) return; 1957 1958 e.preventDefault(); 1959 e.stopPropagation(); 1960 1961 let toggle = item 1962 .closest(".dropdown-wrapper") 1963 .querySelector(".dropdown-wrapper-toggle"); 1964 const dropdownContent = item 1965 .closest(".dropdown-wrapper") 1966 .querySelector(".dropdown-wrapper-content"); 1967 1968 const ref = dropdownContent.getAttribute("data-ref-comp"); 1969 const refWrapper = 1970 ref && document.querySelector(`[data-comp="${ref}"]`); 1971 const refDropdown = dropdownContent.getAttribute("data-comp"); 1972 1973 toggle.classList.toggle("selected"); // Toggle the 'selected' class 1974 1975 // Close other open dropdowns 1976 document 1977 .querySelectorAll(".dropdown-wrapper-toggle.selected") 1978 .forEach(function (openToggle) { 1979 if (openToggle !== toggle) { 1980 openToggle.classList.remove("selected"); 1981 } 1982 }); 1983 1984 // Add event listeners to all <a> elements inside the dropdown content 1985 dropdownContent.querySelectorAll("li > a").forEach(function (listItem) { 1986 if (!listItem.dataset.listenerAdded) { 1987 listItem.dataset.listenerAdded = true; // Mark as added 1988 1989 listItem.addEventListener("click", function (e) { 1990 e.preventDefault(); // Prevent navigation 1991 e.stopPropagation(); 1992 1993 // Get the text content and lang attribute from the clicked <a> 1994 const selectedText = listItem.textContent.trim(); 1995 const selectedLang = listItem.getAttribute("lang"); 1996 const selectedUrl = listItem.getAttribute("href"); // Get the URL of the selected product 1997 item.textContent = selectedText; 1998 item.setAttribute("lang", selectedLang); 1999 toggle.classList.remove("selected"); 2000 2001 if (refDropdown == "language-dropdown") { 2002 initProductDropdown(refWrapper); 2003 updateDropdownPosition(); 2004 filterRefDropdown(selectedLang, refWrapper); 2005 } 2006 // Update the "Next" button with the selected URL and enable it 2007 let nextButtonWrapper = 2008 refWrapper.querySelector(".button-wrapper"); 2009 let nextButton = nextButtonWrapper.querySelector("a"); 2010 2011 if (selectedUrl !== "#") { 2012 nextButton.setAttribute("href", selectedUrl); 2013 nextButton.dataset.leadFormLang = selectedLang || ""; 2014 nextButton.dataset.productName = selectedText; 2015 nextButton.style.pointerEvents = "auto"; 2016 nextButtonWrapper.classList.remove("disabled"); 2017 } else { 2018 nextButtonWrapper.classList.add("disabled"); 2019 nextButton.setAttribute("href", "#"); 2020 nextButton.dataset.leadFormLang = ""; 2021 delete nextButton.dataset.productName; 2022 nextButton.style.pointerEvents = "none"; 2023 } 2024 }); 2025 } 2026 }); 2027 }); 2028 }); 2029 } 2030 2031 function initializeLeadFormModal() { 2032 const triggerSelector = 2033 ".block-inquiry-form .product-references-wrapper .button-wrapper.p-next a"; 2034 const triggers = document.querySelectorAll(triggerSelector); 2035 2036 if (!triggers.length) { 2037 return; 2038 } 2039 2040 let modal = document.querySelector(".lead-form-modal"); 2041 let frame = null; 2042 let detachLeadFormAutoResize = null; 2043 let lockedScrollY = 0; 2044 2045 function lockPageScroll() { 2046 lockedScrollY = window.scrollY || window.pageYOffset || 0; 2047 document.body.style.position = "fixed"; 2048 document.body.style.top = `-${lockedScrollY}px`; 2049 document.body.style.left = "0"; 2050 document.body.style.right = "0"; 2051 document.body.style.width = "100%"; 2052 document.body.style.overflow = "hidden"; 2053 } 2054 2055 function unlockPageScroll() { 2056 document.body.style.position = ""; 2057 document.body.style.top = ""; 2058 document.body.style.left = ""; 2059 document.body.style.right = ""; 2060 document.body.style.width = ""; 2061 document.body.style.overflow = ""; 2062 window.scrollTo(0, lockedScrollY); 2063 } 2064 2065 function clearLeadFormAutoResize() { 2066 if (typeof detachLeadFormAutoResize === "function") { 2067 detachLeadFormAutoResize(); 2068 } 2069 detachLeadFormAutoResize = null; 2070 } 2071 2072 function applyLeadFormModalStyles(frameDoc) { 2073 if (!frameDoc || !frameDoc.head) { 2074 return; 2075 } 2076 2077 if (frameDoc.getElementById("lead-form-modal-inline-styles")) { 2078 return; 2079 } 2080 2081 const style = frameDoc.createElement("style"); 2082 style.id = "lead-form-modal-inline-styles"; 2083 style.textContent = ` 2084 .webform-submission-form input:not([type="checkbox"]):not([type="radio"]):not([type="tel"]), 2085 .webform-submission-form textarea { 2086 min-height: 44px; 2087 border-radius: 3.955px; 2088 border: 1.318px solid #002d72; 2089 box-sizing: border-box; 2090 } 2091 2092 .webform-submission-form input:not([type="checkbox"]):not([type="radio"]):not([type="tel"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not(.form-select):not(.choices__input), 2093 .webform-submission-form textarea { 2094 background: #fff; 2095 } 2096 2097 .webform-submission-form select, 2098 .webform-submission-form .form-select, 2099 .webform-submission-form .choices, 2100 .webform-submission-form .choices__inner, 2101 .webform-submission-form .select2-container .select2-selection, 2102 .webform-submission-form .chosen-container .chosen-single { 2103 width: 100%; 2104 min-height: 44px; 2105 border-radius: 3.955px; 2106 border: 1.318px solid #002d72; 2107 background: #fff; 2108 color: #303030; 2109 box-sizing: border-box; 2110 } 2111 2112 .webform-submission-form .choices__inner, 2113 .webform-submission-form .select2-container .select2-selection, 2114 .webform-submission-form .chosen-container .chosen-single { 2115 padding: 10px 12px; 2116 } 2117 2118 .webform-submission-form .select, 2119 .webform-submission-form .form-select, 2120 .webform-submission-form .choices__inner, 2121 .webform-submission-form .select2-container .select2-selection, 2122 .webform-submission-form .chosen-container .chosen-single { 2123 box-shadow: inset 0 0 0 1px rgba(0, 45, 114, 0.04); 2124 } 2125 2126 .webform-submission-form select, 2127 .webform-submission-form .form-select { 2128 appearance: none; 2129 -webkit-appearance: none; 2130 padding-right: 2.75rem; 2131 background-image: linear-gradient(45deg, transparent 50%, #002d72 50%), linear-gradient(135deg, #002d72 50%, transparent 50%); 2132 background-position: calc(100% - 1.15rem) calc(50% + 0.06rem), calc(100% - 0.82rem) calc(50% + 0.06rem); 2133 background-size: 0.42rem 0.42rem, 0.42rem 0.42rem; 2134 background-repeat: no-repeat; 2135 } 2136 2137 .webform-submission-form .choices[data-type*="select-one"], 2138 .webform-submission-form .select2-container, 2139 .webform-submission-form .chosen-container { 2140 position: relative; 2141 } 2142 2143 .webform-submission-form .select2-container .select2-selection--single, 2144 .webform-submission-form .chosen-container .chosen-single, 2145 .webform-submission-form .choices[data-type*="select-one"] .choices__inner { 2146 position: relative; 2147 padding-right: 2.75rem; 2148 } 2149 2150 .webform-submission-form .select2-container .select2-selection--single::after, 2151 .webform-submission-form .chosen-container .chosen-single::after, 2152 .webform-submission-form .choices[data-type*="select-one"]::after { 2153 content: ""; 2154 position: absolute; 2155 right: 1rem; 2156 top: 50%; 2157 width: 0.55rem; 2158 height: 0.55rem; 2159 border-right: 2px solid #002d72; 2160 border-bottom: 2px solid #002d72; 2161 transform: translateY(-75%) rotate(45deg); 2162 transform-origin: center; 2163 transition: none; 2164 pointer-events: none; 2165 } 2166 2167 .webform-submission-form .iti { 2168 width: 100% !important; 2169 display: block; 2170 } 2171 2172 .webform-submission-form .iti input[type="tel"], 2173 .webform-submission-form .iti input.form-tel { 2174 width: 100%; 2175 min-height: 44px; 2176 padding-right: 12px; 2177 box-sizing: border-box; 2178 } 2179 2180 .webform-submission-form .iti--allow-dropdown input[type="tel"], 2181 .webform-submission-form .iti--allow-dropdown input.form-tel { 2182 padding-left: 56px !important; 2183 border-radius: 3.955px; 2184 } 2185 2186 .webform-submission-form .iti__selected-flag { 2187 height: 100%; 2188 padding: 0 10px; 2189 border-right: 1.318px solid #002d72; 2190 border-top: 0; 2191 border-bottom: 0; 2192 border-left: 0; 2193 border-radius: 0; 2194 background: #fff; 2195 box-sizing: border-box; 2196 } 2197 2198 .webform-submission-form .iti__country-list { 2199 min-width: 280px; 2200 max-width: none; 2201 width: max-content; 2202 box-sizing: border-box; 2203 z-index: 9999; 2204 overflow-y: auto; 2205 max-height: 220px; 2206 } 2207 2208 .webform-submission-form .iti__country-list .iti__country { 2209 display: flex; 2210 align-items: center; 2211 gap: 8px; 2212 white-space: nowrap; 2213 padding: 6px 12px; 2214 } 2215 2216 .webform-submission-form .iti__country-list .iti__country-name { 2217 flex: 1; 2218 overflow: visible; 2219 white-space: nowrap; 2220 } 2221 2222 .webform-submission-form .iti__country-list .iti__dial-code { 2223 flex-shrink: 0; 2224 margin-left: 4px; 2225 } 2226 2227 .webform-submission-form .iti__flag-container { 2228 position: absolute; 2229 z-index: 9999; 2230 } 2231 2232 .webform-submission-form .form-type-checkbox, 2233 .webform-submission-form .js-form-type-checkbox, 2234 .webform-submission-form .webform-type-checkbox { 2235 display: grid; 2236 grid-template-columns: 1.125rem minmax(0, 1fr); 2237 column-gap: 10px; 2238 row-gap: 0.25rem; 2239 align-items: start; 2240 } 2241 2242 .webform-submission-form .form-type-checkbox[style*="display:block"], 2243 .webform-submission-form .form-type-checkbox[style*="display: block"], 2244 .webform-submission-form .js-form-type-checkbox[style*="display:block"], 2245 .webform-submission-form .js-form-type-checkbox[style*="display: block"], 2246 .webform-submission-form .webform-type-checkbox[style*="display:block"], 2247 .webform-submission-form .webform-type-checkbox[style*="display: block"] { 2248 display: grid !important; 2249 } 2250 2251 .webform-submission-form .form-type-checkbox input[type="checkbox"], 2252 .webform-submission-form .js-form-type-checkbox input[type="checkbox"], 2253 .webform-submission-form .webform-type-checkbox input[type="checkbox"] { 2254 grid-column: 1; 2255 grid-row: 1; 2256 margin: 0.2rem 0 0; 2257 width: 1.125rem; 2258 height: 1.125rem; 2259 appearance: none; 2260 -webkit-appearance: none; 2261 border: 1.5px solid #002d72; 2262 border-radius: 0.35rem; 2263 background: #fff; 2264 display: grid; 2265 place-content: center; 2266 cursor: pointer; 2267 box-sizing: border-box; 2268 } 2269 2270 .webform-submission-form .form-type-checkbox input[type="checkbox"]::before, 2271 .webform-submission-form .js-form-type-checkbox input[type="checkbox"]::before, 2272 .webform-submission-form .webform-type-checkbox input[type="checkbox"]::before { 2273 content: ""; 2274 width: 0.62rem; 2275 height: 0.62rem; 2276 transform: scale(0); 2277 transition: transform 120ms ease-in-out; 2278 border-radius: 0.18rem; 2279 background: #002d72; 2280 } 2281 2282 .webform-submission-form .form-type-checkbox input[type="checkbox"]:checked::before, 2283 .webform-submission-form .js-form-type-checkbox input[type="checkbox"]:checked::before, 2284 .webform-submission-form .webform-type-checkbox input[type="checkbox"]:checked::before { 2285 transform: scale(1); 2286 } 2287 2288 .webform-submission-form .form-type-checkbox input[type="checkbox"]:focus-visible, 2289 .webform-submission-form .js-form-type-checkbox input[type="checkbox"]:focus-visible, 2290 .webform-submission-form .webform-type-checkbox input[type="checkbox"]:focus-visible { 2291 outline: 2px solid rgba(0, 45, 114, 0.35); 2292 outline-offset: 2px; 2293 } 2294 2295 .webform-submission-form .form-type-checkbox label, 2296 .webform-submission-form .form-type-checkbox .form-item__label, 2297 .webform-submission-form .form-type-checkbox .description, 2298 .webform-submission-form .form-type-checkbox .form-item__description, 2299 .webform-submission-form .js-form-type-checkbox label, 2300 .webform-submission-form .js-form-type-checkbox .form-item__label, 2301 .webform-submission-form .js-form-type-checkbox .description, 2302 .webform-submission-form .js-form-type-checkbox .form-item__description, 2303 .webform-submission-form .webform-type-checkbox label, 2304 .webform-submission-form .webform-type-checkbox .form-item__label, 2305 .webform-submission-form .webform-type-checkbox .description, 2306 .webform-submission-form .webform-type-checkbox .form-item__description { 2307 grid-column: 2; 2308 margin: 0; 2309 min-width: 0; 2310 } 2311 2312 .webform-submission-form label, 2313 .webform-submission-form .form-item__label, 2314 .webform-submission-form .form-item > label { 2315 color: #002d72; 2316 } 2317 2318 .webform-submission-form .form-actions, 2319 .webform-submission-form [data-drupal-selector="edit-actions"] { 2320 margin-top: 2rem; 2321 padding-top: 0.5rem; 2322 } 2323 2324 .webform-submission-form .webform-button--submit, 2325 .webform-submission-form input[type="submit"] { 2326 display: inline-flex; 2327 align-items: center; 2328 justify-content: center; 2329 margin-top: 0.5rem; 2330 padding: 0.3rem 2.5rem; 2331 border-radius: 100px; 2332 border: 2px solid #002d72; 2333 background-color: #002d72; 2334 color: #fff; 2335 font: var(--fw-400) var(--fs-0) / var(--lh-1) var(--ff-0); 2336 font-weight: var(--fw-500); 2337 text-transform: uppercase; 2338 letter-spacing: 0.02em; 2339 box-shadow: none; 2340 border-radius: 100px !important; 2341 transition: transform 300ms ease, background-color 300ms ease, 2342 border-color 300ms ease; 2343 } 2344 2345 .webform-submission-form .webform-button--submit:hover, 2346 .webform-submission-form .webform-button--submit:focus, 2347 .webform-submission-form .webform-button--submit:focus-visible, 2348 .webform-submission-form input[type="submit"]:hover, 2349 .webform-submission-form input[type="submit"]:focus, 2350 .webform-submission-form input[type="submit"]:focus-visible { 2351 transform: scale(1.07); 2352 cursor: pointer; 2353 } 2354 `; 2355 2356 frameDoc.head.appendChild(style); 2357 } 2358 2359 function closeModal() { 2360 if (!modal) { 2361 return; 2362 } 2363 2364 clearLeadFormAutoResize(); 2365 modal.close(); 2366 unlockPageScroll(); 2367 if (frame) { 2368 frame.setAttribute("src", "about:blank"); 2369 } 2370 } 2371 2372 function buildLeadFormUrl(trigger) { 2373 const selectedHref = trigger.getAttribute("href"); 2374 const selectedLang = (trigger.dataset.leadFormLang || "").toLowerCase(); 2375 const currentPathLang = ( 2376 window.location.pathname.split("/")[1] || "" 2377 ).toLowerCase(); 2378 const langPattern = /^[a-z]{2}(?:-[a-z]{2})?$/; 2379 const normalizeProductSlug = (value) => 2380 value 2381 .toString() 2382 .normalize("NFD") 2383 .replace(/[\u0300-\u036f]/g, "") 2384 .toLowerCase() 2385 .replace(/[^a-z0-9]+/g, "-") 2386 .replace(/^-+|-+$/g, ""); 2387 const extractProductSlugFromPathname = (pathname) => { 2388 co
2388nst segments = pathname.split("/").filter(Boolean); 2389 if (segments.length < 2) { 2390 return ""; 2391 } 2392 2393 if (langPattern.test(segments[0])) { 2394 return decodeURIComponent(segments[1] || ""); 2395 } 2396 2397 return decodeURIComponent(segments[segments.length - 1] || ""); 2398 }; 2399 2400 const leadFormId = 2401 selectedLang === "en-us" || currentPathLang === "en-us" 2402 ? "citraclear-new-lead-form" 2403 : "lead-form"; 2404 2405 const leadFormUrl = new URL( 2406 `/form/${leadFormId}`, 2407 window.location.origin, 2408 ); 2409 2410 if (langPattern.test(selectedLang) && selectedLang !== "other") { 2411 leadFormUrl.pathname = `/${selectedLang}/form/${leadFormId}`; 2412 } else if ( 2413 langPattern.test(currentPathLang) && 2414 currentPathLang !== "other" 2415 ) { 2416 leadFormUrl.pathname = `/${currentPathLang}/form/${leadFormId}`; 2417 } 2418 2419 if (selectedHref && selectedHref !== "#") { 2420 const selectedProductUrl = new URL( 2421 selectedHref, 2422 window.location.origin, 2423 ); 2424 const selectedProductName = ( 2425 trigger.dataset.productName || 2426 trigger 2427 .closest(".product-references-wrapper") 2428 ?.querySelector('[data-comp="ref-button"]')?.textContent || 2429 "" 2430 ).trim(); 2431 const productSlug = 2432 selectedProductUrl.searchParams.get("product") || 2433 extractProductSlugFromPathname(selectedProductUrl.pathname) || 2434 normalizeProductSlug(selectedProductName); 2435 2436 if (productSlug) { 2437 leadFormUrl.searchParams.set("product", productSlug); 2438 } 2439 2440 if (selectedProductName) { 2441 leadFormUrl.searchParams.set("product_name", selectedProductName); 2442 } 2443 } 2444 2445 return leadFormUrl.toString(); 2446 } 2447 2448 function openModal(url) { 2449 if (!url) { 2450 return; 2451 } 2452 2453 if (!modal) { 2454 modal = document.createElement("dialog"); 2455 modal.className = "lead-form-modal"; 2456 modal.innerHTML = ` 2457 <div class="lead-form-modal__panel" role="document"> 2458 <button class="lead-form-modal__close" type="button" aria-label="Close">×</button> 2459 <iframe class="lead-form-modal__iframe" title="Lead form" loading="lazy"></iframe> 2460 </div> 2461 `; 2462 document.body.appendChild(modal); 2463 2464 frame = modal.querySelector(".lead-form-modal__iframe"); 2465 modal 2466 .querySelector(".lead-form-modal__close") 2467 .addEventListener("click", closeModal); 2468 2469 modal.addEventListener("click", (event) => { 2470 if (event.target === modal) { 2471 closeModal(); 2472 } 2473 }); 2474 2475 modal.addEventListener("close", () => { 2476 unlockPageScroll(); 2477 }); 2478 } 2479 2480 if (!frame) { 2481 frame = modal.querySelector(".lead-form-modal__iframe"); 2482 } 2483 2484 frame.onload = () => { 2485 try { 2486 const frameDoc = frame.contentDocument; 2487 if (!frameDoc) { 2488 return; 2489 } 2490 2491 clearLeadFormAutoResize(); 2492 2493 applyLeadFormModalStyles(frameDoc); 2494 const frameRoot = frameDoc.querySelector(".page-main"); 2495 2496 frameDoc.documentElement.style.overflowX = "hidden"; 2497 frameDoc.body.style.overflowX = "hidden"; 2498 frameDoc.body.style.margin = "0"; 2499 frameDoc.documentElement.style.overflowY = "auto"; 2500 frameDoc.body.style.overflowY = "auto"; 2501 2502 let touchLastY = null; 2503 let touchRemainder = 0; 2504 2505 const onFrameTouchStart = (event) => { 2506 if (!event.touches || !event.touches.length) { 2507 touchLastY = null; 2508 return; 2509 } 2510 touchLastY = event.touches[0].clientY; 2511 }; 2512 2513 const onFrameTouchMove = (event) => { 2514 if ( 2515 touchLastY === null || 2516 !event.touches || 2517 !event.touches.length 2518 ) { 2519 return; 2520 } 2521 2522 const currentY = event.touches[0].clientY; 2523 const rawDeltaY = touchLastY - currentY; 2524 touchLastY = currentY; 2525 2526 if (!modal) { 2527 return; 2528 } 2529 2530 // Keep very small movement instead of dropping it to avoid jitter. 2531 const deltaWithRemainder = rawDeltaY + touchRemainder; 2532 const step = 2533 deltaWithRemainder > 0 2534 ? Math.floor(deltaWithRemainder)
2535 : Math.ceil(deltaWithRemainder); 2536 touchRemainder = deltaWithRemainder - step; 2537 2538 if (step !== 0) { 2539 modal.scrollTop += step; 2540 } 2541 2542 event.preventDefault(); 2543 }; 2544 2545 const onFrameTouchEnd = () => { 2546 touchLastY = null; 2547 touchRemainder = 0; 2548 }; 2549 2550 frameDoc.addEventListener("touchstart", onFrameTouchStart, { 2551 passive: true, 2552 capture: true, 2553 }); 2554 frameDoc.addEventListener("touchmove", onFrameTouchMove, { 2555 passive: false, 2556 capture: true, 2557 }); 2558 frameDoc.addEventListener("touchend", onFrameTouchEnd, true); 2559 frameDoc.addEventListener("touchcancel", onFrameTouchEnd, true); 2560 2561 frameDoc 2562 .querySelectorAll( 2563 "header, .region-header, .breadcrumb, .region-breadcrumb, .region-footer, .footer, footer", 2564 ) 2565 .forEach((el) => { 2566 el.style.display = "none"; 2567 }); 2568 2569 let resizeRafId = null; 2570 2571 const scrollModalToTop = () => { 2572 if (modal) { 2573 modal.scrollTop = 0; 2574 } 2575 frameDoc.documentElement.scrollTop = 0; 2576 frameDoc.body.scrollTop = 0; 2577 if (frameRoot) { 2578 frameRoot.scrollIntoView({ block: "start", behavior: "auto" }); 2579 } 2580 }; 2581 2582 const resizeModalToContent = () => { 2583 const isDesktop = window.matchMedia("(min-width: 768px)").matches; 2584 2585 if (frameRoot) { 2586 frameRoot.style.maxWidth = isDesktop ? "60rem" : "100%"; 2587 frameRoot.style.margin = "0 auto"; 2588 frameRoot.style.padding = isDesktop 2589 ? "0 1.75rem 0.75rem" 2590 : "0 1rem 1.5rem"; 2591 frameRoot.style.boxSizing = "border-box"; 2592 } 2593 2594 const contentHeight = Math.max( 2595 frameDoc.documentElement.scrollHeight, 2596 frameDoc.body.scrollHeight, 2597 frameRoot ? frameRoot.scrollHeight : 0, 2598 ); 2599 2600 frame.style.height = `${contentHeight + 4}px`; 2601 frameDoc.documentElement.style.overflowY = "hidden"; 2602 frameDoc.body.style.overflowY = "hidden"; 2603 modal.style.overflowY = "auto"; 2604 modal.style.overscrollBehaviorY = "contain"; 2605 modal.style.webkitOverflowScrolling = "touch"; 2606 }; 2607 2608 const requestResizeModalToContent = () => { 2609 if (resizeRafId !== null) { 2610 cancelAnimationFrame(resizeRafId); 2611 } 2612 2613 resizeRafId = requestAnimationFrame(() => { 2614 resizeRafId = null; 2615 resizeModalToContent(); 2616 }); 2617 }; 2618 2619 const onViewportResize = () => { 2620 requestResizeModalToContent(); 2621 }; 2622 2623 window.addEventListener("resize", onViewportResize); 2624 window.addEventListener("orientationchange", onViewportResize); 2625 2626 frameDoc.addEventListener("input", requestResizeModalToContent, true); 2627 frameDoc.addEventListener( 2628 "change", 2629 requestResizeModalToContent, 2630 true, 2631 ); 2632 frameDoc.addEventListener( 2633 "submit", 2634 () => { 2635 requestResizeModalToContent(); 2636 requestAnimationFrame(scrollModalToTop); 2637 }, 2638 true, 2639 ); 2640 2641 let frameResizeObserver = null; 2642 if (typeof ResizeObserver !== "undefined") { 2643 frameResizeObserver = new ResizeObserver(() => { 2644 requestResizeModalToContent(); 2645 }); 2646 frameResizeObserver.observe(frameDoc.body); 2647 if (frameRoot) { 2648 frameResizeObserver.observe(frameRoot); 2649 } 2650 } 2651 2652 const frameMutationObserver = new MutationObserver(() => { 2653 requestResizeModalToContent(); 2654 }); 2655 frameMutationObserver.observe(frameDoc.body, { 2656 childList: true, 2657 subtree: true, 2658 attributes: true, 2659 characterData: true, 2660 }); 2661 2662 detachLeadFormAutoResize = () => { 2663 window.removeEventListener("resize", onViewportResize); 2664 window.removeEventListener("orientationchange", onViewportResize); 2665 frameDoc.removeEventListener( 2666 "input", 2667 requestResizeModalToContent, 2668 true, 2669 ); 2670 frameDoc.removeEventListener( 2671 "change", 2672 requestResizeModalToContent, 2673 true, 2674 ); 2675 frameDoc.removeEventListener("touchstart", onFrameTouchStart, true); 2676 frameDoc.removeEventListener("touchmove", onFrameTouchMove, true); 2677 frameDoc.removeEventListener("touchend", onFrameTouchEnd, true); 2678 frameDoc.removeEventListener("touchcancel", onFrameTouchEnd, true); 2679 frameMutationObserver.disconnect(); 2680 if (frameResizeObserver) { 2681 frameResizeObserver.disconnect(); 2682 } 2683 if (resizeRafId !== null) { 2684 cancelAnimationFrame(resizeRafId); 2685 resizeRafId = null; 2686 } 2687 }; 2688 2689 requestResizeModalToContent(); 2690 scrollModalToTop(); 2691 setTimeout(requestResizeModalToContent, 200); 2692 setTimeout(requestResizeModalToContent, 700); 2693 setTimeout(scrollModalToTop, 200); 2694 } catch (err) { 2695 // Ignore iframe DOM access issues (e.g. unexpected cross-origin responses). 2696 } 2697 }; 2698 2699 frame.setAttribute("src", url); 2700 2701 if (typeof modal.showModal === "function") { 2702 lockPageScroll(); 2703 modal.showModal(); 2704 modal.scrollTop = 0; 2705 } else { 2706 window.location.href = url; 2707 } 2708 } 2709
2710 triggers.forEach((trigger) => { 2711 trigger.addEventListener("click", (event) => { 2712 const href = trigger.getAttribute("href"); 2713 const url = buildLeadFormUrl(trigger); 2714 2715 if (!href || href === "#") { 2716 return; 2717 } 2718 2719 event.preventDefault(); 2720 event.stopPropagation(); 2721 event.stopImmediatePropagation(); 2722 openModal(url); 2723 }); 2724 }); 2725 } 2726 2727 initializeDropdown(); 2728 initializeLeadFormModal(); 2729} 2730 2731(async function locationPopupUS() { 2732 const markup = `<dialog class="location-detection-popup"> 2733 <div class="location-detection"> 2734 <p class="title">We can see you're in the United States.</p> 2735 <p>Do you want to visit the Bracco US site?</p> 2736 <div class="button-wrapper"> 2737 <dc-login-button size="medium" language="en" loginClientId="" redirectUri="" state=""></dc-login-button> 2738 <a data-theme="transparent" data-color="blue" class="link cancel" href="#">No thanks</a> 2739 <a data-theme="fill" data-color="blue" class="link" href="https://www.bracco.com/en-us">Take me there</a> 2740 </div> 2741 </div> 2742 </dialog>`; 2743 2744 try { 2745 // prevent multi call. 2746 const hasLocationPopup = document.querySelector( 2747 ".location-detection-popup", 2748 ); 2749 if (hasLocationPopup) { 2750 return; 2751 } 2752 // Fetch the locationCode 2753 const fetchedLocationCode = await fetchLocationCode(); 2754 // don't show popup if already on US site 2755 if ( 2756 document.documentElement.lang != "en-us" && 2757 fetchedLocationCode === "en-us" && 2758 sessionStorage.getItem("locationPopupUSDisplayed") === null 2759 ) { 2760 // insert markup 2761 document 2762 .querySelector(".page-main") 2763 .insertAdjacentHTML("beforeend", markup); 2764 // Show the location popup 2765 const locationPopup = document.querySelector(".location-detection-popup"); 2766 locationPopup.showModal(); 2767 2768 locationPopup.querySelectorAll("a").forEach((link) => { 2769 link.addEventListener("click", function (e) { 2770 e.preventDefault(); 2771 // Save data to sessionStorage 2772 sessionStorage.setItem("locationPopupUSDisplayed", "true"); 2773 if (link.classList.contains("cancel")) { 2774 locationPopup.close(); 2775 } else { 2776 window.location.href = 2777 drupalSettings.pathToProductUs === null 2778 ? drupalSettings.pathToHomepageUs 2779 : drupalSettings.pathToProductUs; 2780 } 2781 }); 2782 }); 2783 locationPopup.addEventListener("close", function () { 2784 sessionStorage.getItem("locationPopupUSDisplayed") === null && 2785 sessionStorage.setItem("locationPopupUSDisplayed", "true"); 2786 }); 2787 } 2788 } catch (error) { 2789 console.error("Error presetting language:", error); 2790 } 2791})(); 2792 2793(async function htmlPopup() { 2794 try { 2795 const htmlPopup = document.querySelector(".block-html-popup"); 2796 if (!htmlPopup) { 2797 return; 2798 } 2799 2800 const getBackgroundImageUrl = (element) => { 2801 const backgroundImage = window 2802 .getComputedStyle(element) 2803 .getPropertyValue("background-image"); 2804 const match = backgroundImage.match(/^url\(["']?(.*?)["']?\)$/); 2805 return match ? match[1] : ""; 2806 }; 2807 2808 const applyPopupSizeFromImage = (image) => { 2809 if (!image.naturalWidth || !image.naturalHeight) { 2810 return; 2811 } 2812 2813 const rootFontSize = 2814 parseFloat( 2815 window.getComputedStyle(document.documentElement).fontSize, 2816 ) || 16; 2817 const maxWidth = Math.min(window.innerWidth * 0.92, 48 * rootFontSize); 2818 const maxHeight = Math.min(window.innerHeight * 0.88, 52 * rootFontSize); 2819 const ratio = image.naturalWidth / image.naturalHeight; 2820 2821 let popupWidth = Math.floor(maxWidth); 2822 let popupHeight = Math.floor(popupWidth / ratio); 2823 2824 if (popupHeight > maxHeight) { 2825 popupHeight = Math.floor(maxHeight); 2826 popupWidth = Math.floor(popupHeight * ratio); 2827 } 2828 2829 htmlPopup.style.setProperty("--popup-dynamic-width", `${popupWidth}px`); 2830 htmlPopup.style.setProperty("--popup-dynamic-height", `${popupHeight}px`); 2831 }; 2832 2833 const syncPopupSizeWithImage = () => {
2834 const imageUrl = getBackgroundImageUrl(htmlPopup); 2835 if (!imageUrl) { 2836 htmlPopup.style.removeProperty("--popup-dynamic-width"); 2837 htmlPopup.style.removeProperty("--popup-dynamic-height"); 2838 return; 2839 } 2840 2841 const img = new Image(); 2842 img.onload = () => applyPopupSizeFromImage(img); 2843 img.src = imageUrl; 2844 }; 2845 2846 syncPopupSizeWithImage(); 2847 2848 let popupResizeTimer; 2849 window.addEventListener("resize", () => { 2850 clearTimeout(popupResizeTimer); 2851 popupResizeTimer = setTimeout(syncPopupSizeWithImage, 120); 2852 }); 2853 2854 htmlPopup.addEventListener("click", function (event) { 2855 const popupBounds = htmlPopup.getBoundingClientRect(); 2856 const clickedOutsidePopup = 2857 event.clientX < popupBounds.left || 2858 event.clientX > popupBounds.right || 2859 event.clientY < popupBounds.top || 2860 event.clientY > popupBounds.bottom; 2861 2862 if (clickedOutsidePopup) { 2863 htmlPopup.close(); 2864 } 2865 }); 2866 2867 setTimeout(function () { 2868 htmlPopup.showModal(); 2869 }, 1000); 2870 } catch (error) { 2871 console.error("Error showing html popup:", error); 2872 } 2873})(); 2874 2875// Hero highlight ticker: auto-scroll text only when it overflows. 2876function initHeroHighlightTicker() { 2877 const textContainers = document.querySelectorAll( 2878 ".block-hero-highlight .news-strip__text, .layout__region--hero-highlight .news-strip__text", 2879 ); 2880 2881 textContainers.forEach((container) => { 2882 let track = container.querySelector(".news-strip__text-track"); 2883 let mainText = container.querySelector(".news-strip__text-main"); 2884 2885 if (!track || !mainText) { 2886 track = document.createElement("div"); 2887 track.className = "news-strip__text-track"; 2888 2889 mainText = document.createElement("div"); 2890 mainText.className = "news-strip__text-main"; 2891 2892 while (container.firstChild) { 2893 mainText.appendChild(container.firstChild); 2894 } 2895 2896 track.appendChild(mainText); 2897 container.appendChild(track); 2898 } 2899 2900 const oldClone = track.querySelector(".news-strip__text-clone"); 2901 if (oldClone) { 2902 oldClone.remove(); 2903 } 2904 2905 track.style.animation = "none"; 2906 track.style.transform = "translateX(0)"; 2907 2908 // Skip hidden containers; ticker will be re-initialized on load/resize. 2909 if (container.clientWidth < 8) { 2910 return; 2911 } 2912 2913 const hasOverflow = mainText.scrollWidth > container.clientWidth + 2; 2914 if (!hasOverflow) { 2915 return; 2916 } 2917 2918 const clone = document.createElement("div"); 2919 clone.className = "news-strip__text-clone"; 2920 clone.innerHTML = mainText.innerHTML; 2921 track.appendChild(clone); 2922 2923 const gap = 48; // Matches CSS left padding used on clone. 2924 const distance = mainText.scrollWidth + gap; 2925 const speed = 40; // px/s 2926 const duration = Math.max(12, distance / speed); 2927 2928 track.style.setProperty("--news-strip-scroll-distance", `-${distance}px`); 2929 track.style.animation = `newsStripTicker ${duration}s linear infinite`; 2930 }); 2931} 2932 2933if (document.readyState === "loading") { 2934 document.addEventListener("DOMContentLoaded", initHeroHighlightTicker); 2935} else { 2936 initHeroHighlightTicker(); 2937} 2938 2939let heroHighlightResizeTimer; 2940window.addEventListener("resize", () => { 2941 clearTimeout(heroHighlightResizeTimer); 2942 heroHighlightResizeTimer = setTimeout(initHeroHighlightTicker, 150); 2943}); 2944 2945window.addEventListener("load", initHeroHighlightTicker); 2946 2947// Dosage Calculator 2948function initDosageCalculator() { 2949 if (document.querySelector(".block-dosage-calculator")) { 2950 const volumeMatrix = [ 2951 { 2952 volume: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2953 quantity: [ 2954 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 2955 ], 2956 }, 2957 { 2958 volume: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2959 quantity: [ 2960 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 2961 14.0, 2962 ], 2963 }, 2964 { 2965 volume: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28], 2966 quantity: [ 2967 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 2968 14.0, 2969 ], 2970 }, 2971 ]; 2972 2973 function calculate() { 2974 const value = parseFloat(document.getElementById("inputValue").value); 2975 const errorMsg = document.getElementById("error-message"); 2976 errorMsg.textContent = ""; 2977 2978 if (value >= 10 && value <= 140) { 2979 const index = (value - 10) / 10; 2980 const lowerIndex = Math.floor(index); 2981 const upperIndex = Math.ceil(index); 2982 const t = index - lowerIndex; 2983
2984 volumeMatrix.forEach((row, i) => { 2985 const volLower = row.volume[lowerIndex]; 2986 const volUpper = row.volume[upperIndex]; 2987 const qtyLower = row.quantity[lowerIndex]; 2988 const qtyUpper = row.quantity[upperIndex]; 2989 2990 const interpolatedVol = volLower + (volUpper - volLower) * t; 2991 const interpolatedQty = qtyLower + (qtyUpper - qtyLower) * t; 2992 2993 // Volume formatting 2994 const formattedVol = 2995 interpolatedVol % 1 === 0 2996 ? interpolatedVol.toFixed(0) // show as integer 2997 : interpolatedVol.toFixed(2); // show 2 decimals 2998 2999 // Quantity formatting 3000 const formattedQty = 3001 interpolatedQty % 1 === 0 3002 ? interpolatedQty.toFixed(1) // show 1 decimal (e.g., 2.0) 3003 : interpolatedQty.toFixed(2); // show 1 decimal (e.g., 2.3) 3004 3005 document.getElementById(`volume${i + 1}`).textContent = formattedVol; 3006 document.getElementById(`quantity${i + 1}`).textContent = 3007 formattedQty; 3008 }); 3009 } else { 3010 clearValues(); 3011 errorMsg.textContent = "Please enter a number between 10 and 140."; 3012 } 3013 } 3014 3015 function clearResults() { 3016 document.getElementById("inputValue").value = ""; 3017 clearValues(); 3018 document.getElementById("error-message").textContent = ""; 3019 } 3020 3021 function clearValues() { 3022 for (let i = 1; i <= 3; i++) { 3023 document.getElementById(`volume${i}`).textContent = ""; 3024 document.getElementById(`quantity${i}`).textContent = ""; 3025 } 3026 } 3027 3028 const calculateBtn = document.getElementById("calculateBtn"); 3029 const clearBtn = document.getElementById("clearBtn"); 3030 const inputField = document.getElementById("inputValue"); 3031 const rows = document.querySelectorAll(".row"); 3032 3033 if (calculateBtn) calculateBtn.addEventListener("click", calculate); 3034 if (clearBtn) clearBtn.addEventListener("click", clearResults); 3035 if (inputField) { 3036 inputField.addEventListener("input", () => { 3037 clearValues(); 3038 }); 3039 } 3040 3041 const observer = new ResizeObserver((entries) => { 3042 entries.forEach((entry) => { 3043 const row = entry.target; 3044 const height = entry.contentRect.height; 3045 3046 row.style.setProperty("--row-height", `${height}px`); 3047 if (rows.length > 0) { 3048 const firstRowHeight = rows[0].offsetHeight; 3049 3050 rows.forEach((row, index) => { 3051 const height = row.offsetHeight; 3052 const textWrappers = row.querySelectorAll(".text-wrapper"); 3053 3054 row.style.setProperty("--row-height", `${height}px`); 3055 3056 if (index !== 0) { 3057 row.style.setProperty( 3058 "--row-height-first", 3059 `${firstRowHeight}px`, 3060 ); 3061 } 3062 3063 textWrappers.forEach((wrapper) => { 3064 const wrapperHeight = wrapper.offsetHeight; 3065 wrapper.style.setProperty( 3066 "--text-wrapper-height", 3067 `${wrapperHeight}px`, 3068 ); 3069 row.style.setProperty( 3070 `--text-wrapper-height`, 3071 `${wrapperHeight}px`, 3072 ); 3073 }); 3074 }); 3075 } 3076 }); 3077 }); 3078 3079 if (rows) rows.forEach((row) => observer.observe(row)); 3080 } 3081} 3082initDosageCalculator(); 3083 3084let mobileView = false; 3085 3086window.addEventListener("mediaMobile", () => { 3087 mobileView = true; 3088 collapseMenu(); 3089}); 3090window.addEventListener("mediaDesktop", () => { 3091 mobileView = false; 3092 collapseMenu(); 3093 // initMainNavMenu(); 3094}); 3095 3096window.addEventListener("resize", () => { 3097 breakpointHandler(); 3098 // ------------------- 3099 // Header height 3100 // ------------------- 3101 // if (window.innerWidth > 1024) { 3102 // mobileView = false; 3103 // } else { 3104 // mobileView = true; 3105 // } 3106 // setMenuHeight(mobileView); 3107}); 3108 3109window.addEventListener("load", () => { 3110 breakpointHandler(); 3111 initDosageCalculator(); 3112 // ------------------- 3113 // Header height 3114 // ------------------- 3115 // setMenuHeight(); 3116});
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.