1/******************************************************************************* 2 * Copyright 2018 Adobe 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17/** 18 * Element.matches() 19 * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill 20 */ 21if (!Element.prototype.matches) { 22 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 23} 24 25// eslint-disable-next-line valid-jsdoc 26/** 27 * Element.closest() 28 * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill 29 */ 30if (!Element.prototype.closest) { 31 Element.prototype.closest = function(s) { 32 "use strict"; 33 var el = this; 34 if (!document.documentElement.contains(el)) { 35 return null; 36 } 37 do { 38 if (el.matches(s)) { 39 return el; 40 } 41 el = el.parentElement || el.parentNode; 42 } while (el !== null && el.nodeType === 1); 43 return null; 44 }; 45} 46 47/******************************************************************************* 48 * Copyright 2018 Adobe 49 * 50 * Licensed under the Apache License, Version 2.0 (the "License"); 51 * you may not use this file except in compliance with the License. 52 * You may obtain a copy of the License at 53 * 54 * http://www.apache.org/licenses/LICENSE-2.0 55 * 56 * Unless required by applicable law or agreed to in writing, software 57 * distributed under the License is distributed on an "AS IS" BASIS, 58 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 59 * See the License for the specific language governing permissions and 60 * limitations under the License. 61 ******************************************************************************/ 62/* global 63 CQ 64 */ 65(function() { 66 "use strict"; 67 68 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 69 if (!containerUtils) { 70 // eslint-disable-next-line no-console 71 console.warn("Tabs: container utilities at window.CQ.CoreComponents.container.utils are not available. This can lead to missing features. Ensure the core.wcm.components.commons.site.container client library is included on the page."); 72 } 73 var dataLayerEnabled; 74 var dataLayer; 75 var dataLayerName; 76 77 var NS = "cmp"; 78 var IS = "tabs"; 79 80 var keyCodes = { 81 END: 35, 82 HOME: 36, 83 ARROW_LEFT: 37, 84 ARROW_UP: 38, 85 ARROW_RIGHT: 39, 86 ARROW_DOWN: 40 87 }; 88 89 var selectors = { 90 self: "[data-" + NS + '-is="' + IS + '"]', 91 active: { 92 tab: "cmp-tabs__tab--active", 93 tabpanel: "cmp-tabs__tabpanel--active" 94 } 95 }; 96 97 /** 98 * Tabs Configuration 99 * 100 * @typedef {Object} TabsConfig Represents a Tabs configuration 101 * @property {HTMLElement} element The HTMLElement representing the Tabs 102 * @property {Object} options The Tabs options 103 */ 104 105 /** 106 * Tabs 107 * 108 * @class Tabs 109 * @classdesc An interactive Tabs component for navigating a list of tabs 110 * @param {TabsConfig} config The Tabs configuration 111 */ 112 function Tabs(config) { 113 var that = this; 114 115 if (config && config.element) { 116 init(config); 117 } 118 119 /** 120 * Initializes the Tabs 121 * 122 * @private 123 * @param {TabsConfig} config The Tabs configuration 124 */ 125 function init(config) { 126 that._config = config; 127 128 // prevents multiple initialization 129 config.element.removeAttribute("data-" + NS + "-is"); 130 131 cacheElements(config.element); 132 that._active = getActiveIndex(that._elements["tab"]); 133 134 if (that._elements.tabpanel) { 135 refreshActive(); 136 bindEvents(); 137 scrollToDeepLinkIdInTabs(); 138 } 139 140 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 141 /* 142 * Editor message handling: 143 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
144 * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component 145 * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data 146 */ 147 CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 148 CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 149 if (message.data && message.data.type === "cmp-tabs" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 150 if (message.data.operation === "navigate") { 151 navigate(message.data.index); 152 } 153 } 154 }); 155 } 156 } 157 158 /** 159 * Displays the panel containing the element that corresponds to the deep link in the URI fragment 160 * and scrolls the browser to this element. 161 */ 162 function scrollToDeepLinkIdInTabs() { 163 if (containerUtils) { 164 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "tab", "tabpanel"); 165 if (deepLinkItemIdx > -1) { 166 var deepLinkItem = that._elements["tab"][deepLinkItemIdx]; 167 if (deepLinkItem && that._elements["tab"][that._active].id !== deepLinkItem.id) { 168 navigateAndFocusTab(deepLinkItemIdx, true); 169 } 170 var hashId = window.location.hash.substring(1); 171 if (hashId) { 172 var hashItem = document.querySelector("[id='" + hashId + "']"); 173 if (hashItem) { 174 hashItem.scrollIntoView(); 175 } 176 } 177 } 178 } 179 } 180 181 /** 182 * Returns the index of the active tab, if no tab is active returns 0 183 * 184 * @param {Array} tabs Tab elements 185 * @returns {Number} Index of the active tab, 0 if none is active 186 */ 187 function getActiveIndex(tabs) { 188 if (tabs) { 189 for (var i = 0; i < tabs.length; i++) { 190 if (tabs[i].classList.contains(selectors.active.tab)) { 191 return i; 192 } 193 } 194 } 195 return 0; 196 } 197 198 /** 199 * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API 200 * 201 * @private 202 * @param {HTMLElement} wrapper The Tabs wrapper element 203 */ 204 function cacheElements(wrapper) { 205 that._elements = {}; 206 that._elements.self = wrapper; 207 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 208 209 for (var i = 0; i < hooks.length; i++) { 210 var hook = hooks[i]; 211 if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own tab elements 212 var capitalized = IS; 213 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 214 var key = hook.dataset[NS + "Hook" + capitalized]; 215 if (that._elements[key]) { 216 if (!Array.isArray(that._elements[key])) { 217 var tmp = that._elements[key]; 218 that._elements[key] = [tmp]; 219 } 220 that._elements[key].push(hook); 221 } else { 222 that._elements[key] = hook; 223 } 224 } 225 } 226 } 227 228 /** 229 * Binds Tabs event handling 230 * 231 * @private 232 */ 233 function bindEvents() { 234 window.addEventListener("hashchange", scrollToDeepLinkIdInTabs, false); 235 var tabs = that._elements["tab"]; 236 if (tabs) { 237 for (var i = 0; i < tabs.length; i++) { 238 (function(index) { 239 tabs[i].addEventListener("click", function(event) { 240 navigateAndFocusTab(index); 241 }); 242 tabs[i].addEventListener("keydown", function(event) { 243 onKeyDown(event); 244 }); 245 })(i); 246 } 247 } 248 } 249 250 /** 251 * Handles tab keydown events 252 * 253 * @private 254 * @param {Object} event The keydown event 255 */ 256 function onKeyDown(event) { 257 var index = that._active; 258 var lastIndex = that._elements["tab"].length - 1; 259 260 switch (event.keyCode) { 261 case keyCodes.ARROW_LEFT: 262 case keyCodes.ARROW_UP: 263 event.preventDefault(); 264 if (index > 0) { 265 navigateAndFocusTab(index - 1); 266 } 267 break; 268 case keyCodes.ARROW_RIGHT: 269 case keyCodes.ARROW_DOWN: 270 event.preventDefault(); 271 if (index < lastIndex) { 272 navigateAndFocusTab(index + 1); 273 } 274 break; 275 case keyCodes.HOME: 276 event.preventDefault(); 277 navigateAndFocusTab(0); 278 break; 279 case keyCodes.END: 280 event.preventDefault(); 281 navigateAndFocusTab(lastIndex); 282 break; 283 default: 284 return; 285 } 286 } 287 288 /** 289 * Refreshes the tab markup based on the current {@code Tabs#_active} index 290 * 291 * @private 292 */ 293 function refreshActive() { 294 var tabpanels = that._elements["tabpanel"]; 295 var tabs = that._elements["tab"]; 296 297 if (tabpanels) { 298 if (Array.isArray(tabpanels)) { 299 for (var i = 0; i < tabpanels.length; i++) { 300 if (i === parseInt(that._active)) { 301 tabpanels[i].classList.add(selectors.active.tabpanel); 302 tabpanels[i].removeAttribute("aria-hidden"); 303 tabs[i].classList.add(selectors.active.tab); 304 tabs[i].setAttribute("aria-selected", true); 305 tabs[i].setAttribute("tabindex", "0"); 306 } else { 307 tabpanels[i].classList.remove(selectors.active.tabpanel); 308 tabpanels[i].setAttribute("aria-hidden", true); 309 tabs[i].classList.remove(selectors.active.tab); 310 tabs[i].setAttribute("aria-selected", false); 311 tabs[i].setAttribute("tabindex", "-1"); 312 } 313 } 314 } else { 315 // only one tab 316 tabpanels.classList.add(selectors.active.tabpanel); 317 tabs.classList.add(selectors.active.tab); 318 } 319 } 320 } 321 322 /** 323 * Focuses the element and prevents scrolling the element into view 324 * 325 * @param {HTMLElement} element Element to focus 326 */ 327 function focusWithoutScroll(element) { 328 var x = window.scrollX || window.pageXOffset; 329 var y = window.scrollY || window.pageYOffset; 330 element.focus(); 331 window.scrollTo(x, y); 332 } 333 334 /** 335 * Navigates to the tab at the provided index 336 * 337 * @private 338 * @param {Number} index The index of the tab to navigate to 339 */ 340 function navigate(index) { 341 that._active = index; 342 refreshActive(); 343 } 344 345 /** 346 * Navigates to the item at the provided index and ensures the active tab gains focus 347 * 348 * @private 349 * @param {Number} index The index of the item to navigate to 350 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 351 */ 352 function navigateAndFocusTab(index, keepHash) { 353 var exActive = that._active; 354 if (!keepHash && containerUtils) { 355 containerUtils.updateUrlHash(that, "tab", index); 356 } 357 navigate(index); 358 focusWithoutScroll(that._elements["tab"][index]); 359 360 if (dataLayerEnabled) { 361 362 var activeItem = getDataLayerId(that._elements.tabpanel[index]); 363 var exActiveItem = getDataLayerId(that._elements.tabpanel[exActive]); 364 365 dataLayer.push({ 366 event: "cmp:show", 367 eventInfo: { 368 path: "component." + activeItem 369 } 370 }); 371 372 dataLayer.push({ 373 event: "cmp:hide", 374 eventInfo: { 375 path: "component." + exActiveItem 376 } 377 }); 378 379 var tabsId = that._elements.self.id; 380 var uploadPayload = { component: {} }; 381 uploadPayload.component[tabsId] = { shownItems: [activeItem] }; 382 383 var removePayload = { component: {} }; 384 removePayload.component[tabsId] = { shownItems: undefined }; 385 386 dataLayer.push(removePayload); 387 dataLayer.push(uploadPayload); 388 } 389 } 390 } 391 392 /**
393 * Reads options data from the Tabs wrapper element, defined via {@code data-cmp-*} data attributes 394 * 395 * @private 396 * @param {HTMLElement} element The Tabs element to read options data from 397 * @returns {Object} The options read from the component data attributes 398 */ 399 function readData(element) { 400 var data = element.dataset; 401 var options = []; 402 var capitalized = IS; 403 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 404 var reserved = ["is", "hook" + capitalized]; 405 406 for (var key in data) { 407 if (Object.prototype.hasOwnProperty.call(data, key)) { 408 var value = data[key]; 409 410 if (key.indexOf(NS) === 0) { 411 key = key.slice(NS.length); 412 key = key.charAt(0).toLowerCase() + key.substring(1); 413 414 if (reserved.indexOf(key) === -1) { 415 options[key] = value; 416 } 417 } 418 } 419 } 420 421 return options; 422 } 423 424 /** 425 * Parses the dataLayer string and returns the ID 426 * 427 * @private 428 * @param {HTMLElement} item the accordion item 429 * @returns {String} dataLayerId or undefined 430 */ 431 function getDataLayerId(item) { 432 if (item) { 433 if (item.dataset.cmpDataLayer) { 434 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 435 } else { 436 return item.id; 437 } 438 } 439 return null; 440 } 441 442 /** 443 * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. 444 * 445 * @private 446 */ 447 function onDocumentReady() { 448 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 449 if (dataLayerEnabled) { 450 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 451 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 452 } 453 454 var elements = document.querySelectorAll(selectors.self); 455 for (var i = 0; i < elements.length; i++) { 456 new Tabs({ element: elements[i], options: readData(elements[i]) }); 457 } 458 459 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 460 var body = document.querySelector("body"); 461 var observer = new MutationObserver(function(mutations) { 462 mutations.forEach(function(mutation) { 463 // needed for IE 464 var nodesArray = [].slice.call(mutation.addedNodes); 465 if (nodesArray.length > 0) { 466 nodesArray.forEach(function(addedNode) { 467 if (addedNode.querySelectorAll) { 468 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 469 elementsArray.forEach(function(element) { 470 new Tabs({ element: element, options: readData(element) }); 471 }); 472 } 473 }); 474 } 475 }); 476 }); 477 478 observer.observe(body, { 479 subtree: true, 480 childList: true, 481 characterData: true 482 }); 483 } 484 485 if (document.readyState !== "loading") { 486 onDocumentReady(); 487 } else { 488 document.addEventListener("DOMContentLoaded", onDocumentReady); 489 } 490 491 if (containerUtils) { 492 window.addEventListener("load", containerUtils.scrollToAnchor, false); 493 } 494 495}()); 496 497/******************************************************************************* 498 * Copyright 2022 Adobe 499 * 500 * Licensed under the Apache License, Version 2.0 (the "License"); 501 * you may not use this file except in compliance with the License. 502 * You may obtain a copy of the License at 503 * 504 * http://www.apache.org/licenses/LICENSE-2.0 505 * 506 * Unless required by applicable law or agreed to in writing, software 507 * distributed under the License is distributed on an "AS IS" BASIS, 508 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 509 * See the License for the specific language governing permissions and 510 * limitations under the License. 511 ******************************************************************************/ 512(function(document) { 513 "use strict"; 514 515 window.CMP = window.CMP || {}; 516 window.CMP.utils = (function() { 517 var NS = "cmp"; 518 519 /**
520 * Reads options data from the Component wrapper element, defined via {@code data-cmp-*} data attributes 521 * 522 * @param {HTMLElement} element The component element to read options data from 523 * @param {String} is The component identifier 524 * @returns {String[]} The options read from the component data attributes 525 */ 526 var readData = function(element, is) { 527 var data = element.dataset; 528 var options = []; 529 var capitalized = is; 530 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 531 var reserved = ["is", "hook" + capitalized]; 532 533 for (var key in data) { 534 if (Object.prototype.hasOwnProperty.call(data, key)) { 535 var value = data[key]; 536 537 if (key.indexOf(NS) === 0) { 538 key = key.slice(NS.length); 539 key = key.charAt(0).toLowerCase() + key.substring(1); 540 541 if (reserved.indexOf(key) === -1) { 542 options[key] = value; 543 } 544 } 545 } 546 } 547 return options; 548 }; 549 550 /** 551 * Set up the final properties of a component by evaluating the transform function or fall back to the default value on demand 552 * @param {String[]} options the options to transform 553 * @param {Object} properties object of properties of property functions 554 * @returns {Object} transformed properties 555 */ 556 var setupProperties = function(options, properties) { 557 var transformedProperties = {}; 558 559 for (var key in properties) { 560 if (Object.prototype.hasOwnProperty.call(properties, key)) { 561 var property = properties[key]; 562 if (options && options[key] != null) { 563 if (property && typeof property.transform === "function") { 564 transformedProperties[key] = property.transform(options[key]); 565 } else { 566 transformedProperties[key] = options[key]; 567 } 568 } else { 569 transformedProperties[key] = properties[key]["default"]; 570 } 571 } 572 } 573 return transformedProperties; 574 }; 575 576 577 return { 578 readData: readData, 579 setupProperties: setupProperties 580 }; 581 }()); 582}(window.document)); 583 584/******************************************************************************* 585 * Copyright 2018 Adobe 586 * 587 * Licensed under the Apache License, Version 2.0 (the "License"); 588 * you may not use this file except in compliance with the License. 589 * You may obtain a copy of the License at 590 * 591 * http://www.apache.org/licenses/LICENSE-2.0 592 * 593 * Unless required by applicable law or agreed to in writing, software 594 * distributed under the License is distributed on an "AS IS" BASIS, 595 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 596 * See the License for the specific language governing permissions and 597 * limitations under the License. 598 ******************************************************************************/ 599(function() { 600 "use strict"; 601 602 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 603 if (!containerUtils) { 604 // eslint-disable-next-line no-console 605 console.warn("Tabs: container utilities at window.CQ.CoreComponents.container.utils are not available. This can lead to missing features. Ensure the core.wcm.components.commons.site.container client library is included on the page."); 606 } 607 var dataLayerEnabled; 608 var dataLayer; 609 var dataLayerName; 610 611 var NS = "cmp"; 612 var IS = "carousel"; 613 614 var keyCodes = { 615 SPACE: 32, 616 END: 35, 617 HOME: 36, 618 ARROW_LEFT: 37, 619 ARROW_UP: 38, 620 ARROW_RIGHT: 39, 621 ARROW_DOWN: 40 622 }; 623 624 var selectors = { 625 self: "[data-" + NS + '-is="' + IS + '"]' 626 }; 627 628 var properties = { 629 /** 630 * Determines whether the Carousel will automatically transition between slides 631 * 632 * @memberof Carousel
633 * @type {Boolean} 634 * @default false 635 */ 636 "autoplay": { 637 "default": false, 638 "transform": function(value) { 639 return !(value === null || typeof value === "undefined"); 640 } 641 }, 642 /** 643 * Duration (in milliseconds) before automatically transitioning to the next slide 644 * 645 * @memberof Carousel 646 * @type {Number} 647 * @default 5000 648 */ 649 "delay": { 650 "default": 5000, 651 "transform": function(value) { 652 value = parseFloat(value); 653 return !isNaN(value) ? value : null; 654 } 655 }, 656 /** 657 * Determines whether automatic pause on hovering the carousel is disabled 658 * 659 * @memberof Carousel 660 * @type {Boolean} 661 * @default false 662 */ 663 "autopauseDisabled": { 664 "default": false, 665 "transform": function(value) { 666 return !(value === null || typeof value === "undefined"); 667 } 668 } 669 }; 670 671 /** 672 * Carousel Configuration 673 * 674 * @typedef {Object} CarouselConfig Represents a Carousel configuration 675 * @property {HTMLElement} element The HTMLElement representing the Carousel 676 * @property {*[]} options The Carousel options 677 */ 678 679 /** 680 * Carousel 681 * 682 * @class Carousel 683 * @classdesc An interactive Carousel component for navigating a list of generic items 684 * @param {CarouselConfig} config The Carousel configuration 685 */ 686 function Carousel(config) { 687 var that = this; 688 689 if (config && config.element) { 690 init(config); 691 } 692 693 /** 694 * Initializes the Carousel 695 * 696 * @private 697 * @param {CarouselConfig} config The Carousel configuration 698 */ 699 function init(config) { 700 that._config = config; 701 702 // prevents multiple initialization 703 config.element.removeAttribute("data-" + NS + "-is"); 704 705 setupProperties(config.options); 706 cacheElements(config.element); 707 708 that._active = 0; 709 that._paused = false; 710 711 if (that._elements.item) { 712 initializeActive(); 713 bindEvents(); 714 resetAutoplayInterval(); 715 refreshPlayPauseActions(); 716 scrollToDeepLinkIdInCarousel(); 717 } 718 719 // TODO: This section is only relevant in edit mode and should move to the editor clientLib 720 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 721 /* 722 * Editor message handling: 723 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 724 * - check that the message data panel container type is correct and that the id (path) matches this specific Carousel component 725 * - if so, route the "navigate" operation to enact a navigation of the Carousel based on index data 726 */ 727 window.CQ = window.CQ || {}; 728 window.CQ.CoreComponents = window.CQ.CoreComponents || {}; 729 window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 730 window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 731 if (message.data && message.data.type === "cmp-carousel" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 732 if (message.data.operation === "navigate") { 733 navigate(message.data.index); 734 } 735 } 736 }); 737 } 738 } 739 740 /** 741 * Displays the slide containing the element that corresponds to the deep link in the URI fragment 742 * and scrolls the browser to this element. 743 */ 744 function scrollToDeepLinkIdInCarousel() { 745 if (containerUtils) { 746 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "item", "item"); 747 if (deepLinkItemIdx > -1) { 748 var deepLinkItem = that._elements["item"][deepLinkItemIdx]; 749 if (deepLinkItem && that._elements["item"][that._active].id !== deepLinkItem.id) { 750 navigateAndFocusIndicator(deepLinkItemIdx, true); 751 // pause the carousel auto-rotation 752 pause(); 753 } 754 var hashId = window.location.hash.substring(1); 755 if (hashId) { 756 var hashItem = document.querySelector("[id='" + hashId + "']"); 757 if (hashItem) { 758 hashItem.scrollIntoView(); 759 } 760 } 761 } 762 } 763 } 764 765 /** 766 * Caches the Carousel elements as defined via the {@code data-carousel-hook="ELEMENT_NAME"} markup API 767 * 768 * @private 769 * @param {HTMLElement} wrapper The Carousel wrapper element 770 */ 771 function cacheElements(wrapper) { 772 that._elements = {}; 773 that._elements.self = wrapper; 774 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 775 776 for (var i = 0; i < hooks.length; i++) { 777 var hook = hooks[i]; 778 var capitalized = IS; 779 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 780 var key = hook.dataset[NS + "Hook" + capitalized]; 781 if (that._elements[key]) { 782 if (!Array.isArray(that._elements[key])) { 783 var tmp = that._elements[key]; 784 that._elements[key] = [tmp]; 785 } 786 that._elements[key].push(hook); 787 } else { 788 that._elements[key] = hook; 789 } 790 } 791 } 792 793 /** 794 * Sets up properties for the Carousel based on the passed options. 795 * 796 * @private 797 * @param {Object} options The Carousel options 798 */ 799 function setupProperties(options) { 800 that._properties = {}; 801 802 for (var key in properties) { 803 if (Object.prototype.hasOwnProperty.call(properties, key)) { 804 var property = properties[key]; 805 var value = null; 806 807 if (options && options[key] != null) { 808 value = options[key]; 809 810 // transform the provided option 811 if (property && typeof property.transform === "function") { 812 value = property.transform(value); 813 } 814 } 815 816 if (value === null) { 817 // value still null, take the property default 818 value = properties[key]["default"]; 819 } 820 821 that._properties[key] = value; 822 } 823 } 824 } 825 826 /** 827 * Binds Carousel event handling 828 * 829 * @private 830 */ 831 function bindEvents() { 832 window.addEventListener("hashchange", scrollToDeepLinkIdInCarousel, false); 833 if (that._elements["previous"]) { 834 that._elements["previous"].addEventListener("click", function() { 835 var index = getPreviousIndex(); 836 navigate(index); 837 if (dataLayerEnabled) { 838 dataLayer.push({ 839 event: "cmp:show", 840 eventInfo: { 841 path: "component." + getDataLayerId(that._elements.item[index]) 842 } 843 }); 844 } 845 }); 846 } 847 848 if (that._elements["next"]) { 849 that._elements["next"].addEventListener("click", function() { 850 var index = getNextIndex(); 851 navigate(index); 852 if (dataLayerEnabled) { 853 dataLayer.push({ 854 event: "cmp:show", 855 eventInfo: { 856 path: "component." + getDataLayerId(that._elements.item[index]) 857 } 858 }); 859 } 860 }); 861 } 862 863 var indicators = that._elements["indicator"]; 864 if (indicators) { 865 for (var i = 0; i < indicators.length; i++) { 866 (function(index) { 867 indicators[i].addEventListener("click", function(event) { 868 navigateAndFocusIndicator(index); 869 // pause the carousel auto-rotation 870 pause(); 871 }); 872 })(i); 873 } 874 } 875 876 if (that._elements["pause"]) { 877 if (that._properties.autoplay) { 878 that._elements["pause"].addEventListener("click", onPauseClick); 879 } 880 } 881 882 if (that._elements["play"]) { 883 if (that._properties.autoplay) { 884 that._elements["play"].addEventListener("click", onPlayClick); 885 } 886 } 887 888 that._elements.self.addEventListener("keydown", onKeyDown); 889 890 if (!that._properties.autopauseDisabled) { 891 that._elements.self.addEventListener("mouseenter", onMouseEnter); 892 that._elements.self.addEventListener("mouseleave", onMouseLeave); 893 } 894 895 // for accessibility we pause animation when a element get focused 896 var items = that._elements["item"]; 897 if (items) { 898 for (var j = 0; j < items.length; j++) { 899 items[j].addEventListener("focusin", onMouseEnter); 900 items[j].addEventListener("focusout", onMouseLeave); 901 } 902 } 903 } 904 905 /**
906 * Handles carousel keydown events 907 * 908 * @private 909 * @param {Object} event The keydown event 910 */ 911 function onKeyDown(event) { 912 var index = that._active; 913 var lastIndex = that._elements["indicator"].length - 1; 914 915 switch (event.keyCode) { 916 case keyCodes.ARROW_LEFT: 917 case keyCodes.ARROW_UP: 918 event.preventDefault(); 919 if (index > 0) { 920 navigateAndFocusIndicator(index - 1); 921 } 922 break; 923 case keyCodes.ARROW_RIGHT: 924 case keyCodes.ARROW_DOWN: 925 event.preventDefault(); 926 if (index < lastIndex) { 927 navigateAndFocusIndicator(index + 1); 928 } 929 break; 930 case keyCodes.HOME: 931 event.preventDefault(); 932 navigateAndFocusIndicator(0); 933 break; 934 case keyCodes.END: 935 event.preventDefault(); 936 navigateAndFocusIndicator(lastIndex); 937 break; 938 case keyCodes.SPACE: 939 if (that._properties.autoplay && (event.target !== that._elements["previous"] && event.target !== that._elements["next"])) { 940 event.preventDefault(); 941 if (!that._paused) { 942 pause(); 943 } else { 944 play(); 945 } 946 } 947 if (event.target === that._elements["pause"]) { 948 that._elements["play"].focus(); 949 } 950 if (event.target === that._elements["play"]) { 951 that._elements["pause"].focus(); 952 } 953 break; 954 default: 955 return; 956 } 957 } 958 959 /** 960 * Handles carousel mouseenter events 961 * 962 * @private 963 * @param {Object} event The mouseenter event 964 */ 965 function onMouseEnter(event) { 966 clearAutoplayInterval(); 967 } 968 969 /** 970 * Handles carousel mouseleave events 971 * 972 * @private 973 * @param {Object} event The mouseleave event 974 */ 975 function onMouseLeave(event) { 976 resetAutoplayInterval(); 977 } 978 979 /** 980 * Handles pause element click events 981 * 982 * @private 983 * @param {Object} event The click event 984 */ 985 function onPauseClick(event) { 986 pause(); 987 that._elements["play"].focus(); 988 } 989 990 /** 991 * Handles play element click events 992 * 993 * @private 994 * @param {Object} event The click event 995 */ 996 function onPlayClick() { 997 play(); 998 that._elements["pause"].focus(); 999 } 1000 1001 /** 1002 * Pauses the playing of the Carousel. Sets {@code Carousel#_paused} marker. 1003 * Only relevant when autoplay is enabled 1004 * 1005 * @private 1006 */ 1007 function pause() { 1008 that._paused = true; 1009 clearAutoplayInterval(); 1010 refreshPlayPauseActions(); 1011 } 1012 1013 /** 1014 * Enables the playing of the Carousel. Sets {@code Carousel#_paused} marker. 1015 * Only relevant when autoplay is enabled 1016 * 1017 * @private 1018 */ 1019 function play() { 1020 that._paused = false; 1021 1022 // If the Carousel is hovered, don't begin auto transitioning until the next mouse leave event 1023 var hovered = false; 1024 if (that._elements.self.parentElement) { 1025 hovered = that._elements.self.parentElement.querySelector(":hover") === that._elements.self; 1026 } 1027 if (that._properties.autopauseDisabled || !hovered) { 1028 resetAutoplayInterval(); 1029 } 1030 1031 refreshPlayPauseActions(); 1032 } 1033 1034 /** 1035 * Refreshes the play/pause action markup based on the {@code Carousel#_paused} state 1036 * 1037 * @private 1038 */ 1039 function refreshPlayPauseActions() {
1040 setActionDisabled(that._elements["pause"], that._paused); 1041 setActionDisabled(that._elements["play"], !that._paused); 1042 } 1043 1044 /** 1045 * Initialize {@code Carousel#_active} based on the active item of the carousel. 1046 */ 1047 function initializeActive() { 1048 var items = that._elements["item"]; 1049 if (items && Array.isArray(items)) { 1050 for (var i = 0; i < items.length; i++) { 1051 if (items[i].classList.contains("cmp-carousel__item--active")) { 1052 that._active = i; 1053 break; 1054 } 1055 } 1056 } 1057 } 1058 1059 /** 1060 * Refreshes the item markup based on the current {@code Carousel#_active} index 1061 * 1062 * @private 1063 */ 1064 function refreshActive() { 1065 var items = that._elements["item"]; 1066 var indicators = that._elements["indicator"]; 1067 1068 if (items) { 1069 if (Array.isArray(items)) { 1070 for (var i = 0; i < items.length; i++) { 1071 if (i === parseInt(that._active)) { 1072 items[i].classList.add("cmp-carousel__item--active"); 1073 items[i].removeAttribute("aria-hidden"); 1074 indicators[i].classList.add("cmp-carousel__indicator--active"); 1075 indicators[i].setAttribute("aria-selected", true); 1076 indicators[i].setAttribute("tabindex", "0"); 1077 } else { 1078 items[i].classList.remove("cmp-carousel__item--active"); 1079 items[i].setAttribute("aria-hidden", true); 1080 indicators[i].classList.remove("cmp-carousel__indicator--active"); 1081 indicators[i].setAttribute("aria-selected", false); 1082 indicators[i].setAttribute("tabindex", "-1"); 1083 } 1084 } 1085 } else { 1086 // only one item 1087 items.classList.add("cmp-carousel__item--active"); 1088 indicators.classList.add("cmp-carousel__indicator--active"); 1089 } 1090 } 1091 } 1092 1093 /** 1094 * Focuses the element and prevents scrolling the element into view 1095 * 1096 * @param {HTMLElement} element Element to focus 1097 */ 1098 function focusWithoutScroll(element) { 1099 var x = window.scrollX || window.pageXOffset; 1100 var y = window.scrollY || window.pageYOffset; 1101 element.focus(); 1102 window.scrollTo(x, y); 1103 } 1104 1105 /** 1106 * Retrieves the next active index, with looping 1107 * 1108 * @private 1109 * @returns {Number} Index of the next carousel item 1110 */ 1111 function getNextIndex() { 1112 return that._active === (that._elements["item"].length - 1) ? 0 : that._active + 1; 1113 } 1114 1115 /** 1116 * Retrieves the previous active index, with looping 1117 * 1118 * @private 1119 * @returns {Number} Index of the previous carousel item 1120 */ 1121 function getPreviousIndex() { 1122 return that._active === 0 ? (that._elements["item"].length - 1) : that._active - 1; 1123 } 1124 1125 /** 1126 * Navigates to the item at the provided index 1127 * 1128 * @private 1129 * @param {Number} index The index of the item to navigate to 1130 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 1131 */ 1132 function navigate(index, keepHash) { 1133 if (index < 0 || index > (that._elements["item"].length - 1)) { 1134 return; 1135 } 1136 1137 that._active = index; 1138 refreshActive(); 1139 1140 if (!keepHash && containerUtils) { 1141 containerUtils.updateUrlHash(that, "item", index); 1142 } 1143 1144 if (dataLayerEnabled) { 1145 var carouselId = that._elements.self.id; 1146 var activeItem = getDataLayerId(that._elements.item[index]); 1147 var updatePayload = { component: {} }; 1148 updatePayload.component[carouselId] = { shownItems: [activeItem] }; 1149 1150 var removePayload = { component: {} }; 1151 removePayload.component[carouselId] = { shownItems: undefined }; 1152 1153 dataLayer.push(removePayload); 1154 dataLayer.push(updatePayload); 1155 } 1156 1157 // reset the autoplay transition interval following navigation, if not already hovering the carousel
1158 if (that._elements.self.parentElement) { 1159 if (that._elements.self.parentElement.querySelector(":hover") !== that._elements.self) { 1160 resetAutoplayInterval(); 1161 } 1162 } 1163 } 1164 1165 /** 1166 * Navigates to the item at the provided index and ensures the active indicator gains focus 1167 * 1168 * @private 1169 * @param {Number} index The index of the item to navigate to 1170 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 1171 */ 1172 function navigateAndFocusIndicator(index, keepHash) { 1173 navigate(index, keepHash); 1174 focusWithoutScroll(that._elements["indicator"][index]); 1175 1176 if (dataLayerEnabled) { 1177 dataLayer.push({ 1178 event: "cmp:show", 1179 eventInfo: { 1180 path: "component." + getDataLayerId(that._elements.item[index]) 1181 } 1182 }); 1183 } 1184 } 1185 1186 /** 1187 * Starts/resets automatic slide transition interval 1188 * 1189 * @private 1190 */ 1191 function resetAutoplayInterval() { 1192 if (that._paused || !that._properties.autoplay) { 1193 return; 1194 } 1195 clearAutoplayInterval(); 1196 that._autoplayIntervalId = window.setInterval(function() { 1197 if (document.visibilityState && document.hidden) { 1198 return; 1199 } 1200 var indicators = that._elements["indicators"]; 1201 if (indicators !== document.activeElement && indicators.contains(document.activeElement)) { 1202 // if an indicator has focus, ensure we switch focus following navigation 1203 navigateAndFocusIndicator(getNextIndex(), true); 1204 } else { 1205 navigate(getNextIndex(), true); 1206 } 1207 }, that._properties.delay); 1208 } 1209 1210 /** 1211 * Clears/pauses automatic slide transition interval 1212 * 1213 * @private 1214 */ 1215 function clearAutoplayInterval() { 1216 window.clearInterval(that._autoplayIntervalId); 1217 that._autoplayIntervalId = null; 1218 } 1219 1220 /** 1221 * Sets the disabled state for an action and toggles the appropriate CSS classes 1222 * 1223 * @private 1224 * @param {HTMLElement} action Action to disable 1225 * @param {Boolean} [disable] {@code true} to disable, {@code false} to enable 1226 */ 1227 function setActionDisabled(action, disable) { 1228 if (!action) { 1229 return; 1230 } 1231 if (disable !== false) { 1232 action.disabled = true; 1233 action.classList.add("cmp-carousel__action--disabled"); 1234 } else { 1235 action.disabled = false; 1236 action.classList.remove("cmp-carousel__action--disabled"); 1237 } 1238 } 1239 } 1240 1241 /** 1242 * Parses the dataLayer string and returns the ID 1243 * 1244 * @private 1245 * @param {HTMLElement} item the accordion item 1246 * @returns {String} dataLayerId or undefined 1247 */ 1248 function getDataLayerId(item) { 1249 if (item) { 1250 if (item.dataset.cmpDataLayer) { 1251 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 1252 } else { 1253 return item.id; 1254 } 1255 } 1256 return null; 1257 } 1258 1259 /** 1260 * Document ready handler and DOM mutation observers. Initializes Carousel components as necessary. 1261 * 1262 * @private 1263 */ 1264 function onDocumentReady() { 1265 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 1266 if (dataLayerEnabled) { 1267 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 1268 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 1269 } 1270 1271 var elements = document.querySelectorAll(selectors.self); 1272 for (var i = 0; i < elements.length; i++) { 1273 new Carousel({ element: elements[i], options: CMP.utils.readData(elements[i], IS) }); 1274 } 1275 1276 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 1277 var body = document.querySelector("body"); 1278 var observer = new MutationObserver(function(mutations) { 1279 mutations.forEach(function(mutation) { 1280 // needed for IE 1281 var nodesArray = [].slice.call(mutation.addedNodes); 1282 if (nodesArray.length > 0) { 1283 nodesArray.forEach(function(addedNode) { 1284 if (addedNode.querySelectorAll) { 1285 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 1286 elementsArray.forEach(function(element) { 1287 new Carousel({ element: element, options: CMP.utils.readData(element, IS) }); 1288 }); 1289 } 1290 }); 1291 } 1292 }); 1293 }); 1294 1295 observer.observe(body, { 1296 subtree: true, 1297 childList: true, 1298 characterData: true 1299 }); 1300 } 1301 1302 var documentReady = document.readyState !== "loading" ? Promise.resolve() : new Promise(function(resolve) { 1303 document.addEventListener("DOMContentLoaded", resolve); 1304 }); 1305 Promise.all([documentReady]).then(onDocumentReady); 1306 1307 if (containerUtils) { 1308 window.addEventListener("load", containerUtils.scrollToAnchor, false); 1309 } 1310 1311}()); 1312 1313/******************************************************************************* 1314 * Copyright 2017 Adobe 1315 * 1316 * Licensed under the Apache License, Version 2.0 (the "License");
1317 * you may not use this file except in compliance with the License. 1318 * You may obtain a copy of the License at 1319 * 1320 * http://www.apache.org/licenses/LICENSE-2.0 1321 * 1322 * Unless required by applicable law or agreed to in writing, software 1323 * distributed under the License is distributed on an "AS IS" BASIS, 1324 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1325 * See the License for the specific language governing permissions and 1326 * limitations under the License. 1327 ******************************************************************************/ 1328if (window.Element && !Element.prototype.closest) { 1329 // eslint valid-jsdoc: "off" 1330 Element.prototype.closest = 1331 function(s) { 1332 "use strict"; 1333 var matches = (this.document || this.ownerDocument).querySelectorAll(s); 1334 var el = this; 1335 var i; 1336 do { 1337 i = matches.length; 1338 while (--i >= 0 && matches.item(i) !== el) { 1339 // continue 1340 } 1341 } while ((i < 0) && (el = el.parentElement)); 1342 return el; 1343 }; 1344} 1345 1346if (window.Element && !Element.prototype.matches) { 1347 Element.prototype.matches = 1348 Element.prototype.matchesSelector || 1349 Element.prototype.mozMatchesSelector || 1350 Element.prototype.msMatchesSelector || 1351 Element.prototype.oMatchesSelector || 1352 Element.prototype.webkitMatchesSelector || 1353 function(s) { 1354 "use strict"; 1355 var matches = (this.document || this.ownerDocument).querySelectorAll(s); 1356 var i = matches.length; 1357 while (--i >= 0 && matches.item(i) !== this) { 1358 // continue 1359 } 1360 return i > -1; 1361 }; 1362} 1363 1364if (!Object.assign) { 1365 Object.assign = function(target, varArgs) { // .length of function is 2 1366 "use strict"; 1367 if (target === null) { 1368 throw new TypeError("Cannot convert undefined or null to object"); 1369 } 1370 1371 var to = Object(target); 1372 1373 for (var index = 1; index < arguments.length; index++) { 1374 var nextSource = arguments[index]; 1375 1376 if (nextSource !== null) { 1377 for (var nextKey in nextSource) { 1378 if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { 1379 to[nextKey] = nextSource[nextKey]; 1380 } 1381 } 1382 } 1383 } 1384 return to; 1385 }; 1386} 1387 1388(function(arr) { 1389 "use strict"; 1390 arr.forEach(function(item) { 1391 if (Object.prototype.hasOwnProperty.call(item, "remove")) { 1392 return; 1393 } 1394 Object.defineProperty(item, "remove", { 1395 configurable: true, 1396 enumerable: true, 1397 writable: true, 1398 value: function remove() { 1399 this.parentNode.removeChild(this); 1400 } 1401 }); 1402 }); 1403})([Element.prototype, CharacterData.prototype, DocumentType.prototype]); 1404 1405/******************************************************************************* 1406 * Copyright 2022 Adobe 1407 * 1408 * Licensed under the Apache License, Version 2.0 (the "License"); 1409 * you may not use this file except in compliance with the License. 1410 * You may obtain a copy of the License at 1411 * 1412 * http://www.apache.org/licenses/LICENSE-2.0 1413 * 1414 * Unless required by applicable law or agreed to in writing, software 1415 * distributed under the License is distributed on an "AS IS" BASIS, 1416 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1417 * See the License for the specific language governing permissions and 1418 * limitations under the License. 1419 ******************************************************************************/ 1420(function(document) { 1421 "use strict"; 1422 1423 window.CMP = window.CMP || {}; 1424 window.CMP.utils = (function() { 1425 var NS = "cmp"; 1426 1427 /** 1428 * Reads options data from the Component wrapper element, defined via {@code data-cmp-*} data attributes 1429 * 1430 * @param {HTMLElement} element The component element to read options data from 1431 * @param {String} is The component identifier 1432 * @returns {String[]} The options read from the component data attributes 1433 */ 1434 var readData = function(element, is) { 1435 var data = element.dataset; 1436 var options = []; 1437 var capitalized = is; 1438 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 1439 var reserved = ["is", "hook" + capitalized]; 1440 1441 for (var key in data) { 1442 if (Object.prototype.hasOwnProperty.call(data, key)) { 1443 var value = data[key]; 1444 1445 if (key.indexOf(NS) === 0) { 1446 key = key.slice(NS.length); 1447 key = key.charAt(0).toLowerCase() + key.substring(1); 1448 1449 if (reserved.indexOf(key) === -1) { 1450 options[key] = value; 1451 } 1452 } 1453 } 1454 } 1455 return options; 1456 }; 1457 1458 /** 1459 * Set up the final properties of a component by evaluating the transform function or fall back to the default value on demand 1460 * @param {String[]} options the options to transform 1461 * @param {Object} properties object of properties of property functions 1462 * @returns {Object} transformed properties 1463 */ 1464 var setupProperties = function(options, properties) { 1465 var transformedProperties = {}; 1466 1467 for (var key in properties) { 1468 if (Object.prototype.hasOwnProperty.call(properties, key)) { 1469 var property = properties[key]; 1470 if (options && options[key] != null) { 1471 if (property && typeof property.transform === "function") { 1472 transformedProperties[key] = property.transform(options[key]); 1473 } else { 1474 transformedProperties[key] = options[key]; 1475 } 1476 } else { 1477 transformedProperties[key] = properties[key]["default"]; 1478 } 1479 } 1480 } 1481 return transformedProperties; 1482 }; 1483 1484 1485 return { 1486 readData: readData, 1487 setupProperties: setupProperties 1488 }; 1489 }()); 1490}(window.document)); 1491 1492/******************************************************************************* 1493 * Copyright 2022 Adobe 1494 * 1495 * Licensed under the Apache License, Version 2.0 (the "License");
1496 * you may not use this file except in compliance with the License. 1497 * You may obtain a copy of the License at 1498 * 1499 * http://www.apache.org/licenses/LICENSE-2.0 1500 * 1501 * Unless required by applicable law or agreed to in writing, software 1502 * distributed under the License is distributed on an "AS IS" BASIS, 1503 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1504 * See the License for the specific language governing permissions and 1505 * limitations under the License. 1506 ******************************************************************************/ 1507(function(document) { 1508 "use strict"; 1509 1510 window.CMP = window.CMP || {}; 1511 window.CMP.image = window.CMP.image || {}; 1512 window.CMP.image.dynamicMedia = (function() { 1513 var autoSmartCrops = {}; 1514 var SRC_URI_TEMPLATE_WIDTH_VAR = "{.width}"; 1515 var SRC_URI_TEMPLATE_DPR_VAR = "{dpr}"; 1516 var SRC_URI_DPR_OFF = "dpr=off"; 1517 var SRC_URI_DPR_ON = "dpr=on,{dpr}"; 1518 var dpr = window.devicePixelRatio || 1; 1519 var config = { 1520 minWidth: 20 1521 }; 1522 1523 /** 1524 * get auto smart crops from dm 1525 * @param {String} src the src uri 1526 * @returns {{}} the smart crop json object 1527 */ 1528 var getAutoSmartCrops = function(src) { 1529 var request = new XMLHttpRequest(); 1530 var url = src.split(SRC_URI_TEMPLATE_WIDTH_VAR)[0] + "?req=set,json"; 1531 request.open("GET", url, false); 1532 request.onload = function() { 1533 if (request.status >= 200 && request.status < 400) { 1534 // success status 1535 var responseText = request.responseText; 1536 var rePayload = new RegExp(/^(?:\/\*jsonp\*\/)?\s*([^()]+)\(([\s\S]+),\s*"[0-9]*"\);?$/gmi); 1537 var rePayloadJSON = new RegExp(/^{[\s\S]*}$/gmi); 1538 var resPayload = rePayload.exec(responseText); 1539 var payload; 1540 if (resPayload) { 1541 var payloadStr = resPayload[2]; 1542 if (rePayloadJSON.test(payloadStr)) { 1543 payload = JSON.parse(payloadStr); 1544 } 1545 1546 } 1547 // check "relation" - only in case of smartcrop preset 1548 if (payload && payload.set.relation && payload.set.relation.length > 0) { 1549 for (var i = 0; i < payload.set.relation.length; i++) { 1550 autoSmartCrops[parseInt(payload.set.relation[i].userdata.SmartCropWidth)] = 1551 ":" + payload.set.relation[i].userdata.SmartCropDef; 1552 } 1553 } 1554 } else { 1555 // error status 1556 } 1557 }; 1558 request.send(); 1559 return autoSmartCrops; 1560 }; 1561 1562 /** 1563 * Build and return the srcset value based on the available auto smart crops 1564 * @param {String} src the src uri 1565 * @param {Object} smartCrops the smart crops object 1566 * @returns {String} the srcset 1567 */ 1568 var getSrcSet = function(src, smartCrops) { 1569 var srcset; 1570 var keys = Object.keys(smartCrops); 1571 if (keys.length > 0) { 1572 srcset = []; 1573 for (var key in autoSmartCrops) { 1574 srcset.push(src.replace(SRC_URI_TEMPLATE_WIDTH_VAR, smartCrops[key]) + " " + key + "w"); 1575 } 1576 } 1577 return srcset.join(","); 1578 }; 1579 1580 /** 1581 * Get the optimal width based on the available sizes 1582 * @param {[Number]} sizes the available sizes 1583 * @param {Number} width the element width 1584 * @returns {String} the optimal width 1585 */ 1586 function getOptimalWidth(sizes, width) { 1587 var len = sizes.length; 1588 var key = 0; 1589 1590 while ((key < len - 1) && (sizes[key] < width)) { 1591 key++; 1592 } 1593 1594 return sizes[key] !== undefined ? sizes[key].toString() : width; 1595 } 1596 1597 /** 1598 * Get the width of an element or parent element if the width is smaller than the minimum width 1599 * @param {HTMLElement} component the image component 1600 * @param {HTMLElement | Node} parent the parent element 1601 * @returns {Number} the width of the element 1602 */ 1603 var getWidth = function(component, parent) { 1604 var width = component.offsetWidth; 1605 while (width < config.minWidth && parent && !component._autoWidth) { 1606 width = parent.offsetWidth; 1607 parent = parent.parentNode; 1608 } 1609 return width; 1610 }; 1611 1612 /** 1613 * Set the src and srcset attribute for a Dynamic Media Image which auto smart crops enabled. 1614 * @param {HTMLElement} component the image component 1615 * @param {{}} properties the component properties 1616 */ 1617 var setDMAttributes = function(component, properties) { 1618 // for v3 we first have to turn the dpr on
1619 var src = properties.src.replace(SRC_URI_DPR_OFF, SRC_URI_DPR_ON); 1620 src = src.replace(SRC_URI_TEMPLATE_DPR_VAR, dpr); 1621 var smartCrops = {}; 1622 var width; 1623 if (properties["smartcroprendition"] === "SmartCrop:Auto") { 1624 smartCrops = getAutoSmartCrops(src); 1625 } 1626 var hasWidths = (properties.widths && properties.widths.length > 0) || Object.keys(smartCrops).length > 0; 1627 if (hasWidths) { 1628 var image = component.querySelector("img"); 1629 var elemWidth = getWidth(component, component.parentNode); 1630 if (properties["smartcroprendition"] === "SmartCrop:Auto") { 1631 image.setAttribute("srcset", CMP.image.dynamicMedia.getSrcSet(src, smartCrops)); 1632 width = getOptimalWidth(Object.keys(smartCrops, elemWidth)); 1633 image.setAttribute("src", CMP.image.dynamicMedia.getSrc(src, smartCrops[width])); 1634 } else { 1635 width = getOptimalWidth(properties.widths, elemWidth); 1636 image.setAttribute("src", CMP.image.dynamicMedia.getSrc(src, width)); 1637 } 1638 } 1639 }; 1640 1641 /** 1642 * Get the src attribute based on the optimal width 1643 * @param {String} src the src uri 1644 * @param {String} width the element width 1645 * @returns {String} the final src attribute 1646 */ 1647 var getSrc = function(src, width) { 1648 if (src.indexOf(SRC_URI_TEMPLATE_WIDTH_VAR) > -1) { 1649 src = src.replace(SRC_URI_TEMPLATE_WIDTH_VAR, width); 1650 } 1651 return src; 1652 }; 1653 1654 1655 return { 1656 getAutoSmartCrops: getAutoSmartCrops, 1657 getSrcSet: getSrcSet, 1658 getSrc: getSrc, 1659 setDMAttributes: setDMAttributes, 1660 getWidth: getWidth 1661 }; 1662 }()); 1663 document.dispatchEvent(new CustomEvent("core.wcm.components.commons.site.image.dynamic-media.loaded")); 1664}(window.document)); 1665 1666/******************************************************************************* 1667 * Copyright 2016 Adobe 1668 * 1669 * Licensed under the Apache License, Version 2.0 (the "License"); 1670 * you may not use this file except in compliance with the License. 1671 * You may obtain a copy of the License at 1672 * 1673 * http://www.apache.org/licenses/LICENSE-2.0 1674 * 1675 * Unless required by applicable law or agreed to in writing, software 1676 * distributed under the License is distributed on an "AS IS" BASIS, 1677 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1678 * See the License for the specific language governing permissions and 1679 * limitations under the License. 1680 ******************************************************************************/ 1681(function() { 1682 "use strict"; 1683 1684 var NS = "cmp"; 1685 var IS = "image"; 1686 1687 var EMPTY_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; 1688 var LAZY_THRESHOLD_DEFAULT = 0; 1689 var SRC_URI_TEMPLATE_WIDTH_VAR = "{.width}"; 1690 var SRC_URI_TEMPLATE_WIDTH_VAR_ASSET_DELIVERY = "width={width}"; 1691 var SRC_URI_TEMPLATE_DPR_VAR = "{dpr}"; 1692 1693 var selectors = { 1694 self: "[data-" + NS + '-is="' + IS + '"]', 1695 image: '[data-cmp-hook-image="image"]', 1696 map: '[data-cmp-hook-image="map"]', 1697 area: '[data-cmp-hook-image="area"]' 1698 }; 1699 1700 var lazyLoader = { 1701 "cssClass": "cmp-image__image--is-loading", 1702 "style": { 1703 "height": 0, 1704 "padding-bottom": "" // will be replaced with % ratio 1705 } 1706 }; 1707 1708 var properties = { 1709 /** 1710 * An array of alternative image widths (in pixels). 1711 * Used to replace a {.width} variable in the src property with an optimal width if a URI template is provided. 1712 * 1713 * @memberof Image 1714 * @type {Number[]} 1715 * @default [] 1716 */ 1717 "widths": { 1718 "default": [], 1719 "transform": function(value) { 1720 var widths = []; 1721 value.split(",").forEach(function(item) { 1722 item = parseFloat(item); 1723 if (!isNaN(item)) { 1724 widths.push(item); 1725 } 1726 }); 1727 return widths; 1728 } 1729 }, 1730 /** 1731 * Indicates whether the image should be rendered lazily. 1732 * 1733 * @memberof Image 1734 * @type {Boolean} 1735 * @default false 1736 */ 1737 "lazy": { 1738 "default": false, 1739 "transform": function(value) { 1740 return !(value === null || typeof value === "undefined"); 1741 } 1742 }, 1743 /** 1744 * Indicates image is DynamicMedia image. 1745 * 1746 * @memberof Image 1747 * @type {Boolean} 1748 * @default false 1749 */ 1750 "dmimage": { 1751 "default": false, 1752 "transform": function(value) { 1753 return !(value === null || typeof value === "undefined"); 1754 } 1755 }, 1756 /** 1757 * The lazy threshold. 1758 * This is the number of pixels, in advance of becoming visible, when an lazy-loading image should begin 1759 * to load. 1760 * 1761 * @memberof Image 1762 * @type {Number} 1763 * @default 0 1764 */ 1765 "lazythreshold": { 1766 "default": 0, 1767 "transform": function(value) { 1768 var val = parseInt(value); 1769 if (isNaN(val)) { 1770 return LAZY_THRESHOLD_DEFAULT; 1771 } 1772 return val; 1773 } 1774 }, 1775 /** 1776 * The image source. 1777 * 1778 * Can be a simple image source, or a URI template representation that 1779 * can be variable expanded - useful for building an image configuration with an alternative width. 1780 * e.g. '/path/image.coreimg{.width}.jpeg/1506620954214.jpeg' 1781 * 1782 * @memberof Image 1783 * @type {String} 1784 */ 1785 "src": { 1786 "transform": function(value) { 1787 return decodeURIComponent(value); 1788 } 1789 } 1790 }; 1791 1792 var devicePixelRatio = window.devicePixelRatio || 1; 1793 1794 function Image(config) { 1795 var that = this; 1796 1797 var smartCrops = {}; 1798 1799 var useAssetDelivery = false;
1800 var srcUriTemplateWidthVar = SRC_URI_TEMPLATE_WIDTH_VAR; 1801 1802 function init(config) { 1803 // prevents multiple initialization 1804 config.element.removeAttribute("data-" + NS + "-is"); 1805 1806 // check if asset delivery is used 1807 if (config.options.src && config.options.src.indexOf(SRC_URI_TEMPLATE_WIDTH_VAR_ASSET_DELIVERY) >= 0) { 1808 useAssetDelivery = true; 1809 srcUriTemplateWidthVar = SRC_URI_TEMPLATE_WIDTH_VAR_ASSET_DELIVERY; 1810 } 1811 1812 that._properties = CMP.utils.setupProperties(config.options, properties); 1813 cacheElements(config.element); 1814 // check image is DM asset; if true try to make req=set 1815 if (config.options.src && Object.prototype.hasOwnProperty.call(config.options, "dmimage") && (config.options["smartcroprendition"] === "SmartCrop:Auto")) { 1816 smartCrops = CMP.image.dynamicMedia.getAutoSmartCrops(config.options.src); 1817 } 1818 1819 if (!that._elements.noscript) { 1820 return; 1821 } 1822 1823 that._elements.container = that._elements.link ? that._elements.link : that._elements.self; 1824 1825 unwrapNoScript(); 1826 1827 if (that._properties.lazy) { 1828 addLazyLoader(); 1829 } 1830 1831 if (that._elements.map) { 1832 that._elements.image.addEventListener("load", onLoad); 1833 } 1834 1835 window.addEventListener("resize", onWindowResize); 1836 ["focus", "click", "load", "transitionend", "animationend", "scroll"].forEach(function(name) { 1837 document.addEventListener(name, that.update); 1838 }); 1839 1840 that._elements.image.addEventListener("cmp-image-redraw", that.update); 1841 1842 that._interSectionObserver = new IntersectionObserver(function(entries, interSectionObserver) { 1843 entries.forEach(function(entry) { 1844 if (entry.intersectionRatio > 0) { 1845 that.update(); 1846 } 1847 }); 1848 }); 1849 that._interSectionObserver.observe(that._elements.self); 1850 1851 that.update(); 1852 } 1853 1854 function loadImage() { 1855 var hasWidths = (that._properties.widths && that._properties.widths.length > 0) || Object.keys(smartCrops).length > 0; 1856 var replacement; 1857 if (Object.keys(smartCrops).length > 0) { 1858 var optimalWidth = getOptimalWidth(Object.keys(smartCrops), false); 1859 replacement = smartCrops[optimalWidth]; 1860 } else { 1861 replacement = hasWidths ? (that._properties.dmimage ? "" : ".") + getOptimalWidth(that._properties.widths, true) : ""; 1862 } 1863 if (useAssetDelivery) { 1864 replacement = replacement !== "" ? ("width=" + replacement.substring(1)) : ""; 1865 } 1866 var url = that._properties.src.replace(srcUriTemplateWidthVar, replacement); 1867 url = url.replace(SRC_URI_TEMPLATE_DPR_VAR, devicePixelRatio); 1868 1869 var imgSrcAttribute = that._elements.image.getAttribute("src"); 1870 1871 if (url !== imgSrcAttribute) { 1872 if (imgSrcAttribute === null || imgSrcAttribute === EMPTY_PIXEL) { 1873 that._elements.image.setAttribute("src", url); 1874 } else { 1875 var urlTemplateParts = that._properties.src.split(srcUriTemplateWidthVar); 1876 // check if image src was dynamically swapped meanwhile (e.g. by Target) 1877 var isImageRefSame = imgSrcAttribute.startsWith(urlTemplateParts[0]); 1878 if (isImageRefSame && urlTemplateParts.length > 1) { 1879 isImageRefSame = imgSrcAttribute.endsWith(urlTemplateParts[urlTemplateParts.length - 1]); 1880 } 1881 if (isImageRefSame) { 1882 that._elements.image.setAttribute("src", url); 1883 if (!hasWidths) { 1884 window.removeEventListener("scroll", that.update); 1885 } 1886 } 1887 } 1888 } 1889 if (that._lazyLoaderShowing) { 1890 that._elements.image.addEventListener("load", removeLazyLoader); 1891 } 1892 that._interSectionObserver.unobserve(that._elements.self); 1893 } 1894 1895 function getOptimalWidth(widths, useDevicePixelRatio) { 1896 var container = that._elements.self; 1897 var containerWidth = container.clientWidth; 1898 while (containerWidth === 0 && container.parentNode) { 1899 container = container.parentNode; 1900 containerWidth = container.clientWidth; 1901 } 1902 1903 var dpr = useDevicePixelRatio ? devicePixelRatio : 1; 1904 var optimalWidth = containerWidth * dpr; 1905 var len = widths.length; 1906 var key = 0; 1907 1908 while ((key < len - 1) && (widths[key] < optimalWidth)) { 1909 key++; 1910 } 1911 1912 return widths[key].toString(); 1913 } 1914 1915 function addLazyLoader() { 1916 var width = that._elements.image.getAttribute("width"); 1917 var height = that._elements.image.getAttribute("height"); 1918 1919 if (width && height) { 1920 var ratio = (height / width) * 100; 1921 var styles = lazyLoader.style; 1922 1923 styles["padding-bottom"] = ratio + "%"; 1924 1925 for (var s in styles) { 1926 if (Object.prototype.hasOwnProperty.call(styles, s)) { 1927 that._elements.image.style[s] = styles[s]; 1928 } 1929 } 1930 } 1931 that._elements.image.setAttribute("src", EMPTY_PIXEL); 1932 that._elements.image.classList.add(lazyLoader.cssClass); 1933 that._lazyLoaderShowing = true; 1934 } 1935 1936 function unwrapNoScript() { 1937 var markup = decodeNoscript(that._elements.noscript.textContent.trim()); 1938 var parser = new DOMParser(); 1939 1940 // temporary document avoids requesting the image before removing its src 1941 var temporaryDocument = parser.parseFromString(markup, "text/html"); 1942 var imageElement = temporaryDocument.querySelector(selectors.image); 1943 imageElement.removeAttribute("src"); 1944 that._elements.container.insertBefore(imageElement, that._elements.noscript); 1945 1946 var mapElement = temporaryDocument.querySelector(selectors.map); 1947 if (mapElement) { 1948 that._elements.container.insertBefore(mapElement, that._elements.noscript); 1949 } 1950 1951 that._elements.noscript.parentNode.removeChild(that._elements.noscript); 1952 if (that._elements.container.matches(selectors.image)) { 1953 that._elements.image = that._elements.container; 1954 } else { 1955 that._elements.image = that._elements.container.querySelector(selectors.image); 1956 } 1957 1958 that._elements.map = that._elements.container.querySelector(selectors.map); 1959 that._elements.areas = that._elements.container.querySelectorAll(selectors.area); 1960 } 1961 1962 function removeLazyLoader() { 1963 that._elements.image.classList.remove(lazyLoader.cssClass); 1964 for (var property in lazyLoader.style) { 1965 if (Object.prototype.hasOwnProperty.call(lazyLoader.style, property)) { 1966 that._elements.image.style[property] = ""; 1967 } 1968 } 1969 that._elements.image.removeEventListener("load", removeLazyLoader); 1970 that._lazyLoaderShowing = false;
1971 } 1972 1973 function isLazyVisible() { 1974 if (that._elements.container.offsetParent === null) { 1975 return false; 1976 } 1977 1978 var wt = window.pageYOffset; 1979 var wb = wt + document.documentElement.clientHeight; 1980 var et = that._elements.container.getBoundingClientRect().top + wt; 1981 var eb = et + that._elements.container.clientHeight; 1982 1983 return eb >= wt - that._properties.lazythreshold && et <= wb + that._properties.lazythreshold; 1984 } 1985 1986 function resizeAreas() { 1987 if (that._elements.areas && that._elements.areas.length > 0) { 1988 for (var i = 0; i < that._elements.areas.length; i++) { 1989 var width = that._elements.image.width; 1990 var height = that._elements.image.height; 1991 1992 if (width && height) { 1993 var relcoords = that._elements.areas[i].dataset.cmpRelcoords; 1994 if (relcoords) { 1995 var relativeCoordinates = relcoords.split(","); 1996 var coordinates = new Array(relativeCoordinates.length); 1997 1998 for (var j = 0; j < coordinates.length; j++) { 1999 if (j % 2 === 0) { 2000 coordinates[j] = parseInt(relativeCoordinates[j] * width); 2001 } else { 2002 coordinates[j] = parseInt(relativeCoordinates[j] * height); 2003 } 2004 } 2005 2006 that._elements.areas[i].coords = coordinates; 2007 } 2008 } 2009 } 2010 } 2011 } 2012 2013 function cacheElements(wrapper) { 2014 that._elements = {}; 2015 that._elements.self = wrapper; 2016 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 2017 2018 for (var i = 0; i < hooks.length; i++) { 2019 var hook = hooks[i]; 2020 var capitalized = IS; 2021 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2022 var key = hook.dataset[NS + "Hook" + capitalized]; 2023 that._elements[key] = hook; 2024 } 2025 } 2026 2027 function onWindowResize() { 2028 that.update(); 2029 resizeAreas(); 2030 } 2031 2032 function onLoad() { 2033 resizeAreas(); 2034 } 2035 2036 that.update = function() { 2037 if (that._properties.lazy) { 2038 if (isLazyVisible()) { 2039 loadImage(); 2040 } 2041 } else { 2042 loadImage(); 2043 } 2044 }; 2045 2046 if (config && config.element) { 2047 init(config); 2048 } 2049 } 2050 2051 function onDocumentReady() { 2052 var elements = document.querySelectorAll(selectors.self); 2053 for (var i = 0; i < elements.length; i++) { 2054 new Image({ element: elements[i], options: CMP.utils.readData(elements[i], IS) }); 2055 } 2056 2057 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 2058 var body = document.querySelector("body"); 2059 var observer = new MutationObserver(function(mutations) { 2060 mutations.forEach(function(mutation) { 2061 // needed for IE 2062 var nodesArray = [].slice.call(mutation.addedNodes); 2063 if (nodesArray.length > 0) { 2064 nodesArray.forEach(function(addedNode) { 2065 if (addedNode.querySelectorAll) { 2066 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 2067 elementsArray.forEach(function(element) { 2068 new Image({ element: element, options: CMP.utils.readData(element, IS) }); 2069 }); 2070 } 2071 }); 2072 } 2073 }); 2074 }); 2075 2076 observer.observe(body, { 2077 subtree: true, 2078 childList: true, 2079 characterData: true 2080 }); 2081 } 2082 2083 var documentReady = document.readyState !== "loading" ? Promise.resolve() : new Promise(function(resolve) { 2084 document.addEventListener("DOMContentLoaded", resolve); 2085 }); 2086 2087 Promise.all([documentReady]).then(onDocumentReady); 2088 /* 2089 on drag & drop of the component into a parsys, noscript's content will be escaped multiple times by the editor which creates 2090 the DOM for editing; the HTML parser cannot be used here due to the multiple escaping 2091 */ 2092 function decodeNoscript(text) { 2093 text = text.replace(/&(amp;)*lt;/g, "<"); 2094 text = text.replace(/&(amp;)*gt;/g, ">"); 2095 return text; 2096 } 2097 2098})(); 2099 2100/******************************************************************************* 2101 * Copyright 2017 Adobe 2102 * 2103 * Licensed under the Apache License, Version 2.0 (the "License");
2104 * you may not use this file except in compliance with the License. 2105 * You may obtain a copy of the License at 2106 * 2107 * http://www.apache.org/licenses/LICENSE-2.0 2108 * 2109 * Unless required by applicable law or agreed to in writing, software 2110 * distributed under the License is distributed on an "AS IS" BASIS, 2111 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2112 * See the License for the specific language governing permissions and 2113 * limitations under the License. 2114 ******************************************************************************/ 2115(function() { 2116 "use strict"; 2117 2118 var NS = "cmp"; 2119 var IS = "search"; 2120 2121 var DELAY = 300; // time before fetching new results when the user is typing a search string 2122 var LOADING_DISPLAY_DELAY = 300; // minimum time during which the loading indicator is displayed 2123 var PARAM_RESULTS_OFFSET = "resultsOffset"; 2124 2125 var keyCodes = { 2126 TAB: 9, 2127 ENTER: 13, 2128 ESCAPE: 27, 2129 ARROW_UP: 38, 2130 ARROW_DOWN: 40 2131 }; 2132 2133 var selectors = { 2134 self: "[data-" + NS + '-is="' + IS + '"]', 2135 item: { 2136 self: "[data-" + NS + "-hook-" + IS + '="item"]', 2137 title: "[data-" + NS + "-hook-" + IS + '="itemTitle"]', 2138 focused: "." + NS + "-search__item--is-focused" 2139 } 2140 }; 2141 2142 var properties = { 2143 /** 2144 * The minimum required length of the search term before results are fetched. 2145 * 2146 * @memberof Search 2147 * @type {Number} 2148 * @default 3 2149 */ 2150 minLength: { 2151 "default": 3, 2152 transform: function(value) { 2153 value = parseFloat(value); 2154 return isNaN(value) ? null : value; 2155 } 2156 }, 2157 /** 2158 * The maximal number of results fetched by a search request. 2159 * 2160 * @memberof Search 2161 * @type {Number} 2162 * @default 10 2163 */ 2164 resultsSize: { 2165 "default": 10, 2166 transform: function(value) { 2167 value = parseFloat(value); 2168 return isNaN(value) ? null : value; 2169 } 2170 } 2171 }; 2172 2173 var idCount = 0; 2174 2175 function readData(element) { 2176 var data = element.dataset; 2177 var options = []; 2178 var capitalized = IS; 2179 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2180 var reserved = ["is", "hook" + capitalized]; 2181 2182 for (var key in data) { 2183 if (Object.prototype.hasOwnProperty.call(data, key)) { 2184 var value = data[key]; 2185 2186 if (key.indexOf(NS) === 0) { 2187 key = key.slice(NS.length); 2188 key = key.charAt(0).toLowerCase() + key.substring(1); 2189 2190 if (reserved.indexOf(key) === -1) { 2191 options[key] = value; 2192 } 2193 } 2194 } 2195 } 2196 2197 return options; 2198 } 2199 2200 function toggleShow(element, show) { 2201 if (element) { 2202 if (show !== false) { 2203 element.style.display = "block"; 2204 element.setAttribute("aria-hidden", false); 2205 } else { 2206 element.style.display = "none"; 2207 element.setAttribute("aria-hidden", true); 2208 } 2209 } 2210 } 2211 2212 function serialize(form) { 2213 var query = []; 2214 if (form && form.elements) { 2215 for (var i = 0; i < form.elements.length; i++) { 2216 var node = form.elements[i]; 2217 if (!node.disabled && node.name) { 2218 var param = [node.name, encodeURIComponent(node.value)]; 2219 query.push(param.join("=")); 2220 } 2221 } 2222 } 2223 return query.join("&"); 2224 } 2225 2226 function mark(node, regex) { 2227 if (!node || !regex) { 2228 return; 2229 } 2230 2231 // text nodes 2232 if (node.nodeType === 3) { 2233 var nodeValue = node.nodeValue; 2234 var match = regex.exec(nodeValue); 2235 2236 if (nodeValue && match) { 2237 var element = document.createElement("mark"); 2238 element.className = NS + "-search__item-mark"; 2239 element.appendChild(document.createTextNode(match[0])); 2240 2241 var after = node.splitText(match.index); 2242 after.nodeValue = after.nodeValue.substring(match[0].length); 2243 node.parentNode.insertBefore(element, after); 2244 } 2245 } else if (node.hasChildNodes()) { 2246 for (var i = 0; i < node.childNodes.length; i++) { 2247 // recurse 2248 mark(node.childNodes[i], regex); 2249 } 2250 } 2251 } 2252 2253 function Search(config) { 2254 if (config.element) { 2255 // prevents multiple initialization 2256 config.element.removeAttribute("data-" + NS + "-is"); 2257 } 2258 2259 this._cacheElements(config.element); 2260 this._setupProperties(config.options); 2261 2262 this._action = this._elements.form.getAttribute("action"); 2263 this._resultsOffset = 0; 2264 this._hasMoreResults = true; 2265 2266 this._elements.input.addEventListener("input", this._onInput.bind(this)); 2267 this._elements.input.addEventListener("focus", this._onInput.bind(this)); 2268 this._elements.input.addEventListener("keydown", this._onKeydown.bind(this)); 2269 this._elements.clear.addEventListener("click", this._onClearClick.bind(this)); 2270 document.addEventListener("click", this._onDocumentClick.bind(this)); 2271 this._elements.results.addEventListener("scroll", this._onScroll.bind(this)); 2272 2273 this._makeAccessible(); 2274 } 2275 2276 Search.prototype._displayResults = function() { 2277 if (this._elements.input.value.length === 0) { 2278 toggleShow(this._elements.clear, false); 2279 this._cancelResults(); 2280 } else if (this._elements.input.value.length < this._properties.minLength) { 2281 toggleShow(this._elements.clear, true); 2282 } else { 2283 this._updateResults(); 2284 toggleShow(this._elements.clear, true); 2285 } 2286 }; 2287 2288 Search.prototype._onScroll = function(event) { 2289 // fetch new results when the results to be scrolled down are less than the visible results 2290 if (this._elements.results.scrollTop + 2 * this._elements.results.clientHeight >= this._elements.results.scrollHeight) { 2291 this._resultsOffset += this._properties.resultsSize; 2292 this._displayResults(); 2293 } 2294 }; 2295 2296 Search.prototype._onInput = function(event) { 2297 var self = this; 2298 self._cancelResults(); 2299 // start searching when the search term reaches the minimum length 2300 this._timeout = setTimeout(function() { 2301 self._displayResults(); 2302 }, DELAY); 2303 }; 2304 2305 Search.prototype._onKeydown = function(event) { 2306 var self = this; 2307 2308 switch (event.keyCode) { 2309 case keyCodes.TAB: 2310 if (self._resultsOpen()) { 2311 toggleShow(self._elements.results, false); 2312 self._elements.input.setAttribute("aria-expanded", "false"); 2313 } 2314 break;
2315 case keyCodes.ENTER: 2316 event.preventDefault(); 2317 if (self._resultsOpen()) { 2318 var focused = self._elements.results.querySelector(selectors.item.focused); 2319 if (focused) { 2320 focused.click(); 2321 } 2322 } 2323 break; 2324 case keyCodes.ESCAPE: 2325 self._cancelResults(); 2326 break; 2327 case keyCodes.ARROW_UP: 2328 if (self._resultsOpen()) { 2329 event.preventDefault(); 2330 self._stepResultFocus(true); 2331 } 2332 break; 2333 case keyCodes.ARROW_DOWN: 2334 if (self._resultsOpen()) { 2335 event.preventDefault(); 2336 self._stepResultFocus(); 2337 } else { 2338 // test the input and if necessary fetch and display the results 2339 self._onInput(); 2340 } 2341 break; 2342 default: 2343 return; 2344 } 2345 }; 2346 2347 Search.prototype._onClearClick = function(event) { 2348 event.preventDefault(); 2349 this._elements.input.value = ""; 2350 toggleShow(this._elements.clear, false); 2351 toggleShow(this._elements.results, false); 2352 this._elements.input.setAttribute("aria-expanded", "false"); 2353 }; 2354 2355 Search.prototype._onDocumentClick = function(event) { 2356 var inputContainsTarget = this._elements.input.contains(event.target); 2357 var resultsContainTarget = this._elements.results.contains(event.target); 2358 2359 if (!(inputContainsTarget || resultsContainTarget)) { 2360 toggleShow(this._elements.results, false); 2361 this._elements.input.setAttribute("aria-expanded", "false"); 2362 } 2363 }; 2364 2365 Search.prototype._resultsOpen = function() { 2366 return this._elements.results.style.display !== "none"; 2367 }; 2368 2369 Search.prototype._makeAccessible = function() { 2370 var id = NS + "-search-results-" + idCount; 2371 this._elements.input.setAttribute("aria-owns", id); 2372 this._elements.results.id = id; 2373 idCount++; 2374 }; 2375 2376 Search.prototype._generateItems = function(data, results) { 2377 var self = this; 2378 2379 data.forEach(function(item) { 2380 var el = document.createElement("span"); 2381 el.innerHTML = self._elements.itemTemplate.innerHTML; 2382 el.querySelectorAll(selectors.item.title)[0].appendChild(document.createTextNode(item.title)); 2383 el.querySelectorAll(selectors.item.self)[0].setAttribute("href", self._safeHref(item.url)); 2384 results.innerHTML += el.innerHTML; 2385 }); 2386 }; 2387 2388 Search.prototype._safeHref = function(href) { 2389 var a = document.createElement("a"); 2390 a.href = href; 2391 return a.pathname; 2392 }; 2393 2394 Search.prototype._markResults = function() { 2395 var nodeList = this._elements.results.querySelectorAll(selectors.item.self); 2396 var escapedTerm = this._elements.input.value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 2397 var regex = new RegExp("(" + escapedTerm + ")", "gi"); 2398 2399 for (var i = this._resultsOffset - 1; i < nodeList.length; ++i) { 2400 var result = nodeList[i]; 2401 mark(result, regex); 2402 } 2403 }; 2404 2405 Search.prototype._stepResultFocus = function(reverse) { 2406 var results = this._elements.results.querySelectorAll(selectors.item.self); 2407 var focused = this._elements.results.querySelector(selectors.item.focused); 2408 var newFocused; 2409 var index = Array.prototype.indexOf.call(results, focused); 2410 var focusedCssClass = NS + "-search__item--is-focused"; 2411 2412 if (results.length > 0) { 2413 2414 if (!reverse) { 2415 // highlight the next result 2416 if (index < 0) { 2417 results[0].classList.add(focusedCssClass); 2418 results[0].setAttribute("aria-selected", "true"); 2419 } else if (index + 1 < results.length) { 2420 results[index].classList.remove(focusedCssClass); 2421 results[index].setAttribute("aria-selected", "false"); 2422 results[index + 1].classList.add(focusedCssClass); 2423 results[index + 1].setAttribute("aria-selected", "true"); 2424 } 2425 2426 // if the last visible result is partially hidden, scroll up until it's completely visible 2427 newFocused = this._elements.results.querySelector(selectors.item.focused); 2428 if (newFocused) { 2429 var bottomHiddenHeight = newFocused.offsetTop + newFocused.offsetHeight - this._elements.results.scrollTop - this._elements.results.clientHeight; 2430 if (bottomHiddenHeight > 0) { 2431 this._elements.results.scrollTop += bottomHiddenHeight; 2432 } else { 2433 this._onScroll(); 2434 } 2435 } 2436 2437 } else { 2438 // highlight the previous result 2439 if (index >= 1) { 2440 results[index].classList.remove(focusedCssClass); 2441 results[index].setAttribute("aria-selected", "false"); 2442 results[index - 1].classList.add(focusedCssClass); 2443 results[index - 1].setAttribute("aria-selected", "true"); 2444 } 2445 2446 // if the first visible result is partially hidden, scroll down until it's completely visible 2447 newFocused = this._elements.results.querySelector(selectors.item.focused); 2448 if (newFocused) { 2449 var topHiddenHeight = this._elements.results.scrollTop - newFocused.offsetTop; 2450 if (topHiddenHeight > 0) { 2451 this._elements.results.scrollTop -= topHiddenHeight; 2452 } 2453 } 2454 } 2455 } 2456 }; 2457 2458 Search.prototype._updateResults = function() { 2459 var self = this; 2460 if (self._hasMoreResults) { 2461 var request = new XMLHttpRequest(); 2462 var url = self._action + "?" + serialize(self._elements.form) + "&" + PARAM_RESULTS_OFFSET + "=" + self._resultsOffset; 2463 2464 request.open("GET", url, true); 2465 request.onload = function() { 2466 // when the results are loaded: hide the loading indicator and display the search icon after a minimum period 2467 setTimeout(function() { 2468 toggleShow(self._elements.loadingIndicator, false); 2469 toggleShow(self._elements.icon, true); 2470 }, LOADING_DISPLAY_DELAY); 2471 if (request.status >= 200 && request.status < 400) { 2472 // success status 2473 var data = JSON.parse(request.responseText); 2474 if (data.length > 0) { 2475 self._generateItems(data, self._elements.results); 2476 self._markResults(); 2477 toggleShow(self._elements.results, true); 2478 self._elements.input.setAttribute("aria-expanded", "true"); 2479 } else { 2480 self._hasMoreResults = false;
2481 } 2482 // the total number of results is not a multiple of the fetched results: 2483 // -> we reached the end of the query 2484 if (self._elements.results.querySelectorAll(selectors.item.self).length % self._properties.resultsSize > 0) { 2485 self._hasMoreResults = false; 2486 } 2487 } else { 2488 // error status 2489 } 2490 }; 2491 // when the results are loading: display the loading indicator and hide the search icon 2492 toggleShow(self._elements.loadingIndicator, true); 2493 toggleShow(self._elements.icon, false); 2494 request.send(); 2495 } 2496 }; 2497 2498 Search.prototype._cancelResults = function() { 2499 clearTimeout(this._timeout); 2500 this._elements.results.scrollTop = 0; 2501 this._resultsOffset = 0; 2502 this._hasMoreResults = true; 2503 this._elements.results.innerHTML = ""; 2504 this._elements.input.setAttribute("aria-expanded", "false"); 2505 }; 2506 2507 Search.prototype._cacheElements = function(wrapper) { 2508 this._elements = {}; 2509 this._elements.self = wrapper; 2510 var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 2511 2512 for (var i = 0; i < hooks.length; i++) { 2513 var hook = hooks[i]; 2514 var capitalized = IS; 2515 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2516 var key = hook.dataset[NS + "Hook" + capitalized]; 2517 this._elements[key] = hook; 2518 } 2519 }; 2520 2521 Search.prototype._setupProperties = function(options) { 2522 this._properties = {}; 2523 2524 for (var key in properties) { 2525 if (Object.prototype.hasOwnProperty.call(properties, key)) { 2526 var property = properties[key]; 2527 if (options && options[key] != null) { 2528 if (property && typeof property.transform === "function") { 2529 this._properties[key] = property.transform(options[key]); 2530 } else { 2531 this._properties[key] = options[key]; 2532 } 2533 } else { 2534 this._properties[key] = properties[key]["default"]; 2535 } 2536 } 2537 } 2538 }; 2539 2540 function onDocumentReady() { 2541 var elements = document.querySelectorAll(selectors.self); 2542 for (var i = 0; i < elements.length; i++) { 2543 new Search({ element: elements[i], options: readData(elements[i]) }); 2544 } 2545 2546 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 2547 var body = document.querySelector("body"); 2548 var observer = new MutationObserver(function(mutations) { 2549 mutations.forEach(function(mutation) { 2550 // needed for IE 2551 var nodesArray = [].slice.call(mutation.addedNodes); 2552 if (nodesArray.length > 0) { 2553 nodesArray.forEach(function(addedNode) { 2554 if (addedNode.querySelectorAll) { 2555 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 2556 elementsArray.forEach(function(element) { 2557 new Search({ element: element, options: readData(element) }); 2558 }); 2559 } 2560 }); 2561 } 2562 }); 2563 }); 2564 2565 observer.observe(body, { 2566 subtree: true, 2567 childList: true, 2568 characterData: true 2569 }); 2570 } 2571 2572 if (document.readyState !== "loading") { 2573 onDocumentReady(); 2574 } else { 2575 document.addEventListener("DOMContentLoaded", onDocumentReady); 2576 } 2577 2578})(); 2579 2580/******************************************************************************* 2581 * Copyright 2016 Adobe 2582 * 2583 * Licensed under the Apache License, Version 2.0 (the "License"); 2584 * you may not use this file except in compliance with the License. 2585 * You may obtain a copy of the License at 2586 * 2587 * http://www.apache.org/licenses/LICENSE-2.0 2588 * 2589 * Unless required by applicable law or agreed to in writing, softw
2589are 2590 * distributed under the License is distributed on an "AS IS" BASIS, 2591 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2592 * See the License for the specific language governing permissions and 2593 * limitations under the License. 2594 ******************************************************************************/ 2595(function() { 2596 "use strict"; 2597 2598 var NS = "cmp"; 2599 var IS = "formText"; 2600 var IS_DASH = "form-text"; 2601 var SELF_SELECTOR = "[data-" + NS + '-is="' + IS + '"]'; 2602 2603 var selectors = { 2604 self: SELF_SELECTOR, 2605 validationMessage: SELF_SELECTOR + " .cmp-form-text__validation-message" 2606 }; 2607 2608 var displayValidationMessage = false; 2609 2610 var properties = { 2611 /** 2612 * A validation message to display if there is a type mismatch between the user input and expected input. 2613 * 2614 * @type {String} 2615 */ 2616 constraintMessage: "", 2617 /** 2618 * A validation message to display if no input is supplied, but input is expected for the field. 2619 * 2620 * @type {String} 2621 */ 2622 requiredMessage: "" 2623 }; 2624 2625 function readData(element) { 2626 var data = element.dataset; 2627 var options = []; 2628 var capitalized = IS; 2629 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2630 var reserved = ["is", "hook" + capitalized]; 2631 2632 for (var key in data) { 2633 if (Object.prototype.hasOwnProperty.call(data, key)) { 2634 var value = data[key]; 2635 2636 if (key.indexOf(NS) === 0) { 2637 key = key.slice(NS.length); 2638 key = key.charAt(0).toLowerCase() + key.substring(1); 2639 2640 if (reserved.indexOf(key) === -1) { 2641 options[key] = value; 2642 } 2643 } 2644 } 2645 } 2646 2647 return options; 2648 } 2649 2650 function FormText(config) { 2651 if (config.element) { 2652 // prevents multiple initialization 2653 config.element.removeAttribute("data-" + NS + "-is"); 2654 } 2655 2656 this._cacheElements(config.element); 2657 this._setupProperties(config.options); 2658 2659 this._elements.input.addEventListener("invalid", this._onInvalid.bind(this)); 2660 this._elements.input.addEventListener("input", this._onInput.bind(this)); 2661 if (displayValidationMessage) { 2662 this._elements.input.checkValidity(); 2663 } 2664 } 2665 2666 FormText.prototype._onInvalid = function(event) { 2667 event.target.setCustomValidity(""); 2668 if (event.target.validity.typeMismatch) { 2669 if (this._properties.constraintMessage) { 2670 event.target.setCustomValidity(this._properties.constraintMessage); 2671 } 2672 } else if (event.target.validity.valueMissing) { 2673 if (this._properties.requiredMessage) { 2674 event.target.setCustomValidity(this._properties.requiredMessage); 2675 } 2676 } 2677 if (displayValidationMessage) { 2678 var validationMessage = event.target.parentElement.querySelector(".cmp-form-text__validation-message"); 2679 if (validationMessage) { 2680 validationMessage.innerText = event.target.validationMessage; 2681 } 2682 } 2683 }; 2684 2685 FormText.prototype._onInput = function(event) { 2686 event.target.setCustomValidity(""); 2687 if (displayValidationMessage) { 2688 event.target.checkValidity(); 2689 } 2690 }; 2691 2692 FormText.prototype._cacheElements = function(wrapper) { 2693 this._elements = {}; 2694 this._elements.self = wrapper; 2695 var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS_DASH + "]"); 2696 for (var i = 0; i < hooks.length; i++) { 2697 var hook = hooks[i]; 2698 var capitalized = IS; 2699 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2700 var key = hook.dataset[NS + "Hook" + capitalized]; 2701 this._elements[key] = hook; 2702 } 2703 }; 2704 2705 FormText.prototype._setupProperties = function(options) { 2706 this._properties = {}; 2707 2708 for (var key in properties) { 2709 if (Object.prototype.hasOwnProperty.call(properties, key)) { 2710 var property = properties[key]; 2711 if (options && options[key] != null) { 2712 if (property && typeof property.transform === "function") { 2713 this._properties[key] = property.transform(options[key]); 2714 } else { 2715 this._properties[key] = options[key]; 2716 } 2717 } else { 2718 this._properties[key] = properties[key]["default"]; 2719 } 2720 } 2721 } 2722 }; 2723 2724 function onDocumentReady() {
2725 var validationMessages = document.querySelectorAll(selectors.validationMessage); 2726 if (validationMessages && validationMessages.length > 0) { 2727 displayValidationMessage = true; 2728 } 2729 2730 var elements = document.querySelectorAll(selectors.self); 2731 for (var i = 0; i < elements.length; i++) { 2732 new FormText({ element: elements[i], options: readData(elements[i]) }); 2733 } 2734 2735 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 2736 var body = document.querySelector("body"); 2737 var observer = new MutationObserver(function(mutations) { 2738 mutations.forEach(function(mutation) { 2739 // needed for IE 2740 var nodesArray = [].slice.call(mutation.addedNodes); 2741 if (nodesArray.length > 0) { 2742 nodesArray.forEach(function(addedNode) { 2743 if (addedNode.querySelectorAll) { 2744 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 2745 elementsArray.forEach(function(element) { 2746 new FormText({ element: element, options: readData(element) }); 2747 }); 2748 } 2749 }); 2750 } 2751 }); 2752 }); 2753 2754 observer.observe(body, { 2755 subtree: true, 2756 childList: true, 2757 characterData: true 2758 }); 2759 } 2760 2761 if (document.readyState !== "loading") { 2762 onDocumentReady(); 2763 } else { 2764 document.addEventListener("DOMContentLoaded", onDocumentReady); 2765 } 2766 2767})(); 2768 2769$(function() { 2770 var ua = window.navigator.userAgent; 2771 var trident = ua.indexOf('Trident/'); 2772 var msie = ua.indexOf('MSIE '); 2773 if(trident > 0 || msie > 0) 2774 { 2775 $('.cmp-form-text__text[type=date]').datepicker(); 2776 } 2777}); 2778 2779$(document).ready(function() { 2780 var recaptcha = $(".recaptcha-target"); 2781 if (recaptcha.length != 0) { 2782 recaptcha.each(function () { 2783 sitekey = $(this).data('site'); 2784 $(this)[0].innerHTML = ' <div id="forms--recaptcha" class="g-recaptcha" data-sitekey='+sitekey+'></div>'; 2785 }) 2786 } 2787 2788 2789 $(".formContainerComp form").submit(function(event) { 2790 var recaptcha = $("#g-recaptcha-response").val(); 2791 if (recaptcha === "" && $("#g-recaptcha-response").length > 0) { 2792 event.preventDefault(); 2793 if($('.captcha-error').length < 1) 2794 $('.g-recaptcha').prepend("<p class='captcha-error' style='color:red'>This is a required field</p>"); 2795 } 2796 }); 2797}); 2798$(function () { 2799 var $formButton = $('.cmp-form-button'); 2800 2801 var performValidation = function () { 2802 $formButton.on('click', function (e) { 2803 $('.input-message--error').remove(); 2804 $form = $formButton.closest('form'); 2805 var $inputs = $form.find('input'); 2806 for (var i = 0; i < $inputs.length; i++) { 2807 $($inputs[i]).removeClass('input--error'); 2808 if ( 2809 $($inputs[i]).val() === '' && 2810 ($($inputs[i]).prop('required') || $($inputs[i]).hasClass('required')) 2811 ) { 2812 $($inputs[i]).addClass('input--error'); 2813 $($inputs[i]).after( 2814 '<p class="input-message--error">This field is required</p>' 2815 ); 2816 } 2817 } 2818 }); 2819 }; 2820 2821 var isAuthorMode = $('body').hasClass('aem-AuthorLayer-Edit') || 2822 $('body').hasClass('aem-AuthorLayer-Design') || 2823 $('body').hasClass('aem-AuthorLayer-Layouting') || 2824 $('html').hasClass('aem-AuthorLayer-Edit'); 2825 2826 var initConditionalVisibility = function () { 2827 if (isAuthorMode) { 2828 return; 2829 } 2830 2831 var $hiddenElements = $('[data-hidden="true"]'); 2832 2833 $hiddenElements.each(function () { 2834 var $element = $(this); 2835 var conditionalFieldName = $element.attr('data-conditional-field'); 2836 var conditionalValue = $element.attr('data-conditional-value'); 2837 2838 if (!conditionalFieldName || !conditionalValue) { 2839 $element.hide(); 2840 return; 2841 } 2842 2843 var $form = $element.closest('form'); 2844 if (!$form.length) { 2845 $element.hide(); 2846 return; 2847 } 2848 2849 var $conditionalInput = $form.find('[name="' + conditionalFieldName + '"]'); 2850 2851 if (!$conditionalInput.length) { 2852 $element.hide(); 2853 return; 2854 } 2855 2856 var handleVisibilityCheck = function () { 2857 var currentValue = ''; 2858 var inputType = $conditionalInput.first().attr('type'); 2859 2860 if (inputType === 'radio') { 2861 currentValue = $form.find('[name="' + conditionalFieldName + '"]:checked').val() || ''; 2862 } else if (inputType === 'checkbox') { 2863 var checkedValues = []; 2864 $form.find('[name="' + conditionalFieldName + '"]:checked').each(function () { 2865 checkedValues.push($(this).val()); 2866 }); 2867 currentValue = checkedValues.join(', '); 2868 } else { 2869 currentValue = $conditionalInput.first().val() || ''; 2870 } 2871 2872 if (currentValue === conditionalValue) { 2873 $element.show(); 2874 } else { 2875 $element.hide(); 2876 } 2877 }; 2878 2879 $conditionalInput.on('change input', handleVisibilityCheck); 2880 handleVisibilityCheck(); 2881 }); 2882 }; 2883 2884 var logSalesforceErrors = function () { 2885 var params = new URLSearchParams(window.location.search); 2886 var sfStep = params.get('sfStep'); 2887 if (!sfStep) { 2888 return; 2889 } 2890 2891 var sfStatus = params.get('sfStatus') || 'N/A'; 2892 var sfError = params.get('sfError') || 'No response body'; 2893 2894 console.error( 2895 '[Salesforce Error] Step: ' + sfStep + 2896 ' | Status: ' + sfStatus + 2897 ' | Response: ' + sfError 2898 ); 2899 2900 if (window.history && window.history.replaceState) { 2901 var cleanUrl = window.location.pathname + window.location.hash; 2902 window.history.replaceState(null, '', cleanUrl); 2903 } 2904 }; 2905 2906 performValidation(); 2907 initConditionalVisibility(); 2908 logSalesforceErrors(); 2909}); 2910 2911$(function() { 2912 var unavailableDates = []; 2913 var hideWeekend = false;
2914 function unavailable(date) { 2915 2916 2917 var noWeekend = $.datepicker.noWeekends(date); 2918 var year = "" + date.getYear(); 2919 var month = "" + (date.getMonth() + 1); 2920 month = month.length == 1 ? "0"+month : month; 2921 var date = "" + date.getDate(); 2922 date = date.length == 1 ? "0"+date : date; 2923 year = year.substring(1); 2924 2925 dmy = month + "-" +date + "-" + year; 2926 2927 if(hideWeekend && !noWeekend[0]) 2928 { 2929 return [false, "", "Unavailable"]; 2930 } 2931 else if ($.inArray(dmy, unavailableDates) == -1) { 2932 return [true, ""]; 2933 } 2934 else { 2935 return [false, "", "Unavailable"]; 2936 } 2937 } 2938 2939 $('.form--calendar').each(function(){ 2940 var date = $(this).data('date'); 2941 unavailableDates = date ? date.split(',') : ''; 2942 hideWeekend = $(this).data('weekends'); 2943 2944 $(this).datepicker({ 2945 dateFormat: 'mm-dd-yy', 2946 beforeShowDay: unavailable 2947 }); 2948 2949 if($(this).data('required') === 'required') 2950 { 2951 $(this).addClass('required'); 2952 } 2953 }); 2954 2955}); 2956 2957(function() { 2958 $(".video-collection__carousel").each(function(index, element){ 2959 var $this = $(this); 2960 var swiper = new Swiper($this, { 2961 speed: 400, 2962 spaceBetween: 20, 2963 slidesPerView: 4, 2964 grabCursor: true, 2965 navigation: { 2966 nextEl: $this.siblings('.swiper-navigation').children(".swiper-button-next"), 2967 prevEl: $this.siblings('.swiper-navigation').children(".swiper-button-prev"), 2968 }, 2969 pagination: { 2970 el: $this.siblings('.swiper-navigation').children(".swiper-pagination"), 2971 type: 'fraction' 2972 }, 2973 breakpoints: { 2974 // when window width is <= 991px 2975 991: { 2976 slidesPerView: 3, // or 'auto' 2977 }, 2978 2979 767: { 2980 slidesPerView: 1, 2981 slidesPerColumn: 4, 2982 }, 2983 }, 2984 on: { 2985 init: function () { 2986 /* 2987 In order to maintain a 4 ratio per column on mobile 2988 we manually add empty slides until the total amount of slides 2989 is divisible by 4. This will ensure that each column will keep 4 2990 slides and the last column will have those fake slides but they are empty 2991 */ 2992 if ($(window).width() < 768) { 2993 var slidesCount = $this.find('.video__item').length; 2994 while (slidesCount % 4 !== 0) { 2995 this.appendSlide(['<div class="swiper-slide"></div>']) 2996 slidesCount++; 2997 if(slidesCount % 4 === 0) { 2998 this.updateSlides(); 2999 break; 3000 } 3001 } 3002 }//end if 3003 }, 3004 }, 3005 }); 3006 }); 3007})(); 3008$(function () { 3009 3010 var categoryOption = '', 3011 subcategoryOption = '', 3012 currentScreenSize = 'mobile'; 3013 3014 3015 // initial check of screensize 3016 if (window.innerWidth >= 992) { 3017 currentScreenSize = 'desktop'; 3018 } 3019 3020 var mq = window.matchMedia('(min-width: 992px)'); 3021 mq.addEventListener("change", WidthChange); 3022 WidthChange(mq); 3023 3024 // media query change 3025 function WidthChange(mq) { 3026 $('.tips__filter.category select').each(function (ev, obj) { 3027 var $el = $(this), 3028 option = $(this) 3029 .find('option:selected') 3030 .data('option'), 3031 subCategory= $(this) 3032 .closest('.tips__filters') 3033 .find('.subcategory.active li[data-active]') 3034 .data('option'); 3035 if(option && option != 'All Categories') 3036 { 3037 option = option.replace(/:|\//gi, function () { 3038 return '-'; 3039 }) 3040 .toLowerCase(); 3041 } 3042 if(subCategory) 3043 { 3044 subCategory = subCategory.replace(/:|\//gi, function () { 3045 return '-'; 3046 }) 3047 .toLowerCase(); 3048 } 3049 3050 if (mq.matches) { 3051 // window width is at least 992px 3052 currentScreenSize = 'desktop'; 3053 filterRebateResults($el, option, subCategory); 3054 } else { 3055 // window width is less than 992px 3056 currentScreenSize = 'mobile'; 3057 filterRebateResults($el, option, subCategory); 3058 } 3059 }); 3060 } 3061 3062 $('.tips__filter.category select').bind({ 3063 changed: function (ev, obj) { 3064 3065 var $el = $(this), 3066 option = obj.dropdownOption.data('option'); 3067 filterRebateResults($el, option); 3068 showSubcategorySelect($el, option); 3069 } 3070 }); 3071 $('.tips__filter.subcategory select').bind({ 3072 changed: function (ev, obj) { 3073 var $el = $(this), 3074 option = obj.dropdownOption.data('option'); 3075 filterRebateResults($el, categoryOption, option); 3076 } 3077 }); 3078 $('.tips__filter select').selectBoxIt(); 3079 3080 function filterRebateResults($el, category, subcategory) { 3081 // track category, subcategory 3082 categoryOption = category; 3083 subcategoryOption = subcategory; 3084 3085 var tipsComponent = $el.closest('.tipsgrid'); 3086 3087 // filter grid by category, subcategory 3088 var $gridItems = tipsComponent.find('.tips__col'); 3089 var itemsToRemove = []; 3090 3091 $gridItems.each(function (i) { 3092 $(this).show(); 3093 var tags = $(this).attr('data-tags'); 3094 if (tags) { 3095 tags = tags.toLowerCase().split(';'); 3096 } 3097 3098 if (categoryOption != 'All Categories') { 3099 if (categoryOption != '' && categoryOption != undefined) { 3100 categoryOption = category 3101 .replace(/:|\//gi, function () { 3102 return '-'; 3103 }) 3104 .toLowerCase(); 3105 3106 var subcategoryOptionMatches = tags.filter(function(s) { return s.includes(categoryOption) }); 3107 if ((tags && tags.indexOf(categoryOption) < 0) && subcategoryOptionMatches.length <= 0) { 3108 $(this).hide(); 3109 itemsToRemove.push($(this)); 3110 } else { 3111 $(this).show(); 3112 } 3113 } 3114 3115 if (subcategoryOption != '' && subcategoryOption != undefined && subcategoryOption != 'All Categories') { 3116 subcategoryOption = subcategory 3117 .replace(/:|\//gi, function (x) { 3118 return '-'; 3119 }) 3120 .toLowerCase(); 3121 if (tags && tags.indexOf(subcategoryOption) < 0) { 3122 $(this).hide(); 3123 if (itemsToRemove.indexOf(i) < 0) { 3124 itemsToRemove.push($(this)); 3125 } 3126 } else { 3127 if (tags && tags.indexOf(categoryOption) > -1) { 3128 $(this).show(); 3129 } 3130 } 3131 } 3132 } 3133 3134 }); 3135 3136 // if slider, don't pagninate 3137 $slider = tipsComponent.find('.pagination'); 3138 if ($slider) { 3139 // remove items from array before initializing pagination 3140 for (var i = 0; i < itemsToRemove.length; i++) { 3141 for (var j = 0; j < $gridItems.length; j++) { 3142 if ($($gridItems[j]).is(itemsToRemove[i])) { 3143 $gridItems.splice(j, 1); 3144 j--; 3145 } 3146 } 3147 } 3148 3149 // handle pagination 3150 var $pagination = tipsComponent.find('.tips__pagination'); 3151 var $visibleItems = tipsComponent.find('.tips__pages'); 3152 var rowsDesktop = tipsComponent 3153 .find('[data-rows-desktop]') 3154 .attr('data-rows-desktop'); 3155 var rowsMobile = tipsComponent 3156 .find('[data-rows-mobile]') 3157 .attr('data-rows-mobile'); 3158 if (currentScreenSize == 'desktop') { 3159 initPagination($pagination, $gridItems, 3 * rowsDesktop, $visibleItems); 3160 } else { 3161 initPagination($pagination, $gridItems, 2 * rowsMobile, $visibleItems); 3162 } 3163 } 3164 3165 } 3166 3167 3168 function showSubcategorySelect($el, category) { 3169 var tipsComponent = $el.closest('.tipsgrid '); 3170 3171 tipsComponent 3172 .find('.tips__filter.subcategory.active') 3173 .removeClass('active'); 3174 if (category != '') { 3175 var subcategoryFilter = tipsComponent.find( 3176 '.tips__filter.subcategory[data-category="' + category + '"]' 3177 ); 3178 subcategoryFilter.addClass('active'); 3179 var categorySelect = tipsComponent 3180 .find('.tips__filter.category') 3181 .find('select') 3182 .data('selectBox-selectBoxIt'); 3183 var subcategorySelect = subcategoryFilter 3184 .find('select') 3185 .data('selectBox-selectBoxIt'); 3186 if (subcategorySelect) { 3187 var firstOption = subcategorySelect.selectOption(0); 3188 } 3189 } 3190 } 3191}); 3192$(function(){ 3193 var alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]; 3194 3195 setup(); 3196 function setup() 3197 { 3198 resize(); 3199 3200 $('.image-tab__container').each(function(){ 3201 var startingIndex = $(this).data('intro') ? 0 : 1; 3202 findAndSetContentTab($(this), startingIndex); 3203 }); 3204 3205 } 3206 3207 function resize() { 3208 $('.image-tab').each(function(){ 3209 var $pointsContainer = $('.image-tab__points') 3210 $(this).find('.image-tab__points').width( $(this).find('.image-tab--image').width()).height( $(this).find('.image-tab--image').height()) 3211 3212 }); 3213 $('.image-tab__point--container').each(function(){ 3214 $(this).css('top',$(this).data('top')+'%'); 3215 $(this).css('left',$(this).data('left')+'%'); 3216 $(this).css('height',$(this).data('height') +'%'); 3217 $(this).css('width',$(this).data('width') +'%'); 3218 if($(this).data('pointname') === 'alphabet') 3219 { 3220 $(this).find('.image-tab__point').text(alphabet[$(this).data('index') - 1]); 3221 } 3222 //$(this).closest('.image-tab').find('.image-tab--content[data-index=1]').show(); 3223 }); 3224 } 3225 3226 var resizeTimeout; 3227 $(window).on('resize', function(){ 3228 clearTimeout(resizeTimeout) 3229 resizeTimeout = setTimeout(function(){ 3230 resize(); 3231 },500) 3232 }); 3233 3234 3235 $('.image-tab__point--container').click(function(){ 3236 3237 findAndSetContentTab($(this), $(this).data('index')); 3238 $(window).trigger('resize') 3239 3240 }) 3241 3242 function findAndSetContentTab($object, index) 3243 { 3244 var $imageTab = $object.closest('.image-tab'); 3245 $imageTab.find('.image-tab__point--container').removeClass('active'); 3246 $imageTab.find('.image-tab__point--container[data-index='+index+']').addClass('active'); 3247 if(!$imageTab.find('.image-tab__point--container[data-index='+index+']').hasClass('link')) 3248 { 3249 $imageTab.find('.image-tab--content').hide(); 3250 $imageTab.find('.image-tab--content').removeClass('active'); 3251 $imageTab.find('.image-tab--content[data-index='+index+']').show(); 3252 $imageTab.find('.image-tab--content[data-index='+index+']') 3253 } 3254 } 3255 3256 3257}); 3258$(function () { 3259 $('document').ready(function(){ 3260 3261 var fbUrl = 'https://www.facebook.com/sharer.php?u=' + window.location.href; 3262 $('.social--icon--facebook').attr("href",fbUrl ); 3263 3264 var twitterUrl = 'https://twitter.com/intent/tweet?url=' + window.location.href; 3265 $('.social--icon--twitter').attr("href",twitterUrl ); 3266 3267 var linkedInUrl = 'https://www.linkedin.com/sharing/share-offsite/?url=' + window.location.href; 3268 $('.social--icon--linked-in').attr("href",linkedInUrl ); 3269 3270 var mailToUrl = $('.social--icon--email').prop('href'); 3271 var finalMailToUrl = mailToUrl + window.location.href; 3272 3273 $('.social--icon--email').attr("href",finalMailToUrl ); 3274 3275 3276 }) 3277}); 3278$(function () { 3279 $('#inputPassword').keyup(function (event) { 3280 if (event.keyCode == 13) { 3281 $('#loginButton').click(); 3282 } 3283 }); 3284 3285 $('#loginButton').click(function (e) { 3286 e.preventDefault(); 3287 3288 if ($('#rememberMe').is(':checked')) { 3289 user = $('#inputPassword').val(); 3290 if (user != '' && user != null) {
3291 setCookie('usr_c', user, 30); 3292 } 3293 } 3294 3295 var valid = validateForm(); 3296 if (valid) { 3297 $.ajax({ 3298 type: 'POST', 3299 url: $('#url').val(), 3300 data: { 3301 j_username: $('#inputPassword').val(), 3302 j_password: $('#inputPassword').val(), 3303 j_validate: 'true', 3304 }, 3305 success: function (data, textStatus, jqXHR) { 3306 window.location.href = getRedirectPath() + '?t=' + Date.now(); 3307 }, 3308 error: function (XMLHttpRequest, textStatus, errorThrown) { 3309 $('#errordiv').val('Invalid User Name or Password'); 3310 }, 3311 }); 3312 } else { 3313 $('#errordiv').val('Invalid User Name or Password'); 3314 } 3315 }); 3316}); 3317 3318//Function to redirect to homepage after login 3319var getRedirectPath = function () { 3320 return $('#redirectPath').attr('href'); 3321}; 3322 3323// Function for setting cookies at login page 3324var setCookie = function (cname, cvalue, exdays) { 3325 var d = new Date(); 3326 d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); 3327 var expires = 'expires=' + d.toGMTString(); 3328 document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; 3329}; 3330 3331// Function for getting cookies at login page 3332var getCookie = function (cname) { 3333 var name = cname + '='; 3334 var decodedCookie = decodeURIComponent(document.cookie); 3335 var ca = decodedCookie.split(';'); 3336 for (var i = 0; i < ca.length; i++) { 3337 var c = ca[i]; 3338 while (c.charAt(0) == ' ') { 3339 c = c.substring(1); 3340 } 3341 if (c.indexOf(name) == 0) { 3342 return c.substring(name.length, c.length); 3343 } 3344 } 3345 return ''; 3346}; 3347 3348// Function for validating Login form 3349var validateForm = function () { 3350 return $('#inputPassword').val().length > 0; 3351}; 3352 3353$(function(){ 3354 3355 function setupPopper(targetID, popoverID) 3356 { 3357 var popcorn = document.querySelector('#'+targetID); 3358 var tooltip = document.querySelector('#'+popoverID); 3359 Popper.createPopper(popcorn, tooltip, { 3360 placement: 'bottom', 3361 modifiers: [ 3362 { 3363 name: 'offset', 3364 options: { 3365 3366 offset: [0, 10], 3367 }, 3368 }, 3369 { name: 'flip', enabled: false } 3370 ], 3371 }); 3372 } 3373 3374 function setupInputFields() 3375 { 3376 3377 $(".ROICalculator__input--average-usage").on('focusout', function(evt){ 3378 3379 if (evt.which != 110 && $(this).val() ){//not a fullstop 3380 var unit = $('.ROICalculator__unit').val() 3381 var min = 10000; 3382 var max = 500000; 3383 if(unit == 'cubic ft') 3384 { 3385 min = Math.round(min / 7.48); 3386 max = Math.round(max / 7.48); 3387 } 3388 else if(unit == 'cubic meters') 3389 { 3390 min = Math.round(min / 264.172); 3391 max = Math.round(max / 264.172); 3392 } 3393 var n = parseFloat($(this).val().replace(',', '')); 3394 $(this).removeClass('input--error') 3395 if($(this).next().hasClass("input-message--error")) 3396 { 3397 $(this).next().remove(); 3398 } 3399 if(!isNaN(n) && n <= max && n >= min) 3400 { 3401 $(this).val(n.toLocaleString()); 3402 } 3403 else { 3404 var errorMessage = isNaN(n) ? 'A number is required.' :'Range: '+min.toLocaleString()+'-'+max.toLocaleString()+' '+unit+' allowed'; 3405 $(this).addClass('input--error') 3406 $(this).after('<p class="input-message--error">'+errorMessage+'</p>'); 3407 } 3408 } 3409 }); 3410 3411 //jQuery 3412 $(".ROICalculator__number:not(.ROICalculator__input--average-usage)").on('focusout', function(evt){ 3413 3414 if (evt.which != 110 && $(this).val() ){//not a fullstop 3415 var min = $(this).data("min") 3416 var max = $(this).data("max") 3417 var n = parseFloat($(this).val().replace(',', '')); 3418 $(this).removeClass('input--error') 3419 if($(this).next().hasClass("input-message--error")) 3420 { 3421 $(this).next().remove(); 3422 } 3423 if(!isNaN(n) && n <= max && n >= min) 3424 {
3425 $(this).val(n.toLocaleString()); 3426 } 3427 else { 3428 var errorMessage = isNaN(n) ? 'A number is required.' :'Range: '+min.toLocaleString()+'-'+max.toLocaleString()+' allowed'; 3429 $(this).addClass('input--error') 3430 $(this).after('<p class="input-message--error">'+errorMessage+'</p>'); 3431 } 3432 } 3433 }); 3434 3435 $(".ROICalculator__percent").on('focusout', function(evt){ 3436 var min = $(this).data("min") 3437 var max = $(this).data("max") 3438 if($(this).val()) 3439 { 3440 var n = parseFloat($(this).val().replace(',', '').replace('%', '')); 3441 $(this).removeClass('input--error') 3442 if($(this).next().hasClass("input-message--error")) 3443 { 3444 $(this).next().remove(); 3445 } 3446 if(!isNaN(n) && n <= max && n >= min) 3447 { 3448 $(this).val(n.toLocaleString() + '%'); 3449 } 3450 else { 3451 var errorMessage = isNaN(n) ? 'A number is required.' : 'Range: '+min.toLocaleString()+'-'+max.toLocaleString()+'% allowed' 3452 $(this).addClass('input--error') 3453 $(this).after('<p class="input-message--error">'+errorMessage+'</p>'); 3454 } 3455 } 3456 }); 3457 } 3458 3459 function formatTableData(dataItem) 3460 { 3461 return "<td>" + dataItem + "</td>" 3462 } 3463 3464 function formatTableDataInfo(dataItemName, dataItemValue, message) 3465 { 3466 var html = "<div>"; 3467 html += message ? '<span data-popover="'+message+'">' : ''; 3468 html += "<p class='ROICalculator__table-info--title'>" + dataItemName + "</p>"; 3469 html += message ? '</span>' : '' 3470 html += "<p class='ROICalculator__table-info--content'>" + dataItemValue + "</p>"; 3471 html += "</div>" 3472 return html; 3473 } 3474 3475 function formatSavingsDataInfo(dataItemName, dataItemValue, message) 3476 { 3477 var html = "<div>"; 3478 3479 html += "<p class='ROICalculator__savings--title'>" + dataItemName 3480 html += '<span class="popover--center" data-popover="'+message+'">' 3481 html += '<a class="ROICalculator__popover"></a>' 3482 html += '</span>' 3483 html += "</p>"; 3484 html += "<p class='ROICalculator__savings--content'>" + dataItemValue + "</p>"; 3485 html += "</div>" 3486 return html; 3487 3488 } 3489 3490 function numberWithCommas(x, noDecimals) { 3491 if(noDecimals) 3492 { 3493 return parseInt(x).toLocaleString(); 3494 } 3495 return x.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); 3496 } 3497 3498 function formatTableMobile(data) 3499 { 3500 $( ".ROICalculator__table tbody" ).empty(); 3501 data.forEach(function(dataItem){ 3502 var html = "<tr class='ROICalculator__table-info--header'>"; 3503 html += formatTableData(dataItem["year"]) 3504 html += formatTableData('$'+numberWithCommas(dataItem["totalActualCost"],true)) 3505 html += formatTableData('$'+numberWithCommas(dataItem["totalBillableCost"],true)) 3506 html += formatTableData('$'+numberWithCommas(dataItem["additionalRevenue"],true)) 3507 html += "<td class='ROICalculator__table-arrow--container'><div class='ROICalculator__table-arrow'></div></td>"; 3508 html += "</tr>" 3509 html += "<tr class='ROICalculator__table-info'><td><div class='ROICalculator__table-info--empty-column'></div></td><td colspan='4'>" 3510 html += "<div style='display:none;' class='ROICalculator__table-info--container'>" 3511 html += formatTableDataInfo("Usage", dataItem["usage"].toLocaleString(),"Based on Average Usage per year (gal/yr)") 3512 html += formatTableDataInfo("100% <br> Charge", '$'+numberWithCommas(dataItem["hundredPercentCharge"]), "Based on measurement of 100% of water usage and the total cost of water") 3513 html += formatTableDataInfo("Actual <br> Charge", '$'+numberWithCommas(dataItem["actualCharge"]),"Based on measurement of reduced of water measurement with PD meter and the total cost of water") 3514 html += "</div>" 3515 html += "<div style='display:none;' class='ROICalculator__table-info--container'>" 3516 if(dataItem["accuracyLost"] == 0) 3517 { 3518 html += formatTableDataInfo("Accuracy <br> Loss", "N/A", "PD meter water usage (reduced due to mechanical wear of PD meter)") 3519 } 3520 else { 3521 html += formatTableDataInfo("Accuracy <br> Loss", numberWithCommas(dataItem["accuracyLost"], true), "PD meter water usage (reduced due to mechanical wear of PD meter)") 3522 } 3523 3524 html += formatTableDataInfo("Cost <br> Sewer", '$'+numberWithCommas(dataItem["costSewer"]),"Sewer Rate (based on 1000 gallons)") 3525 html += formatTableDataInfo("Cost <br> Water", '$'+numberWithCommas(dataItem["costWater"]), "Water Rate (based on 1000 gals)") 3526 html += "</div>" 3527 html += "<div style='display:none;' class='ROICalculator__table-info--container'>" 3528 html += formatTableDataInfo("Total <br> Cost", '$'+numberWithCommas(dataItem["totalCost"]),"Water rate plus sewer rate (based on 1000 gallons)") 3529 html += formatTableDataInfo("Difference", '$'+numberWithCommas(dataItem["difference"]), "Difference between 100% charge and Actual charge") 3530 html += formatTableDataInfo("Low Flow <br> Accuracy", numberWithCommas(dataItem["lowFlowAccuracy"], true), "Additional gallons measured due to iPERL's low flow measurement capabilities") 3531 html += "</div>" 3532 html += "<div style='display:none;' class='ROICalculator__table-info--container'>" 3533 html += formatTableDataInfo("Added <br> Revenue", '$'+numberWithCommas(dataItem["addedRevenue"]), "Additional per meter revenue as a result of the increased low flow measurement with iPERL multiplied by the total cost of water") 3534 html += formatTableDataInfo("Total Added <br> Revenue", '$'+numberWithCommas(dataItem["totalAddedRevenue"]),"Total added per meter revenue due to iPERL overall accuracy plus added low flow revenue") 3535 html += formatTableDataInfo("", '') 3536 html += "</div>" 3537 html += "</td></tr>" 3538 html += "</tr>" 3539 $('.ROICalculator__table-body').append(html); 3540 }) 3541 3542 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Difference", '$'+numberWithCommas(data[19]["totalDifference"]),"Difference between 100% charge and Actual charge")); 3543 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Added Revenue",'$'+ numberWithCommas(data[19]["sumAddedRevenue"]),"Additional per meter revenue as a result of the increased low flow measurement with iPERL multiplied by the total cost of water")); 3544 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Added Revenue",'$'+ numberWithCommas(data[19]["sumTotalAddedRevenue"]), "Total added per meter revenue due to iPERL overall accuracy plus added low flow revenue")); 3545 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Actual $", '$'+numberWithCommas(data[19]["sumTotalActualAmount"], true),"100% Charge field plus Added Revenue field due to iPERL's low flow capabilities multiplied by the total number of meters (Cost Sewer)")); 3546 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Billable $", '$'+numberWithCommas(data[19]["sumTotalBillableAmou
3546nt"], true),"Actual billed amount: Actual Charge multiplied by the total number of meters (Cost Sewer)")); 3547 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Additional Revenue", '$'+numberWithCommas(data[19]["totalAdditionalRevenue"], true),"Additional revenue per year as a result of iPERL's perpetual accuracy and low flow measurement capabilities")); 3548 3549 } 3550 3551 3552 function formatTableDesktop(data) 3553 { 3554 $( ".ROICalculator__table tbody" ).empty(); 3555 data.forEach(function(dataItem){ 3556 var html = "<tr class='ROICalculator__table-info--header'>"; 3557 html += formatTableData(dataItem["year"]) 3558 html += formatTableData(dataItem["usage"].toLocaleString()) 3559 html += formatTableData('$'+numberWithCommas(dataItem["hundredPercentCharge"])) 3560 html += formatTableData('$'+numberWithCommas(dataItem["actualCharge"])) 3561 html += formatTableData('$'+numberWithCommas(dataItem["totalActualCost"], true)) 3562 html += formatTableData('$'+numberWithCommas(dataItem["totalBillableCost"], true)) 3563 html += formatTableData('$'+numberWithCommas(dataItem["additionalRevenue"], true)) 3564 html += "<td class='ROICalculator__table-arrow--container'><div class='ROICalculator__table-arrow'></div></td>"; 3565 html += "<tr class='ROICalculator__table-info'><td class='ROICalculator--fold'><div class='ROICalculator__table-info--empty-column'></div></td><td class='ROICalculator--fold' colspan='8'><div style='display:none;' class='ROICalculator__table-info--container'>" 3566 if(dataItem["accuracyLost"] == 0) 3567 { 3568 html += formatTableDataInfo("Accuracy <br> Loss", "N/A", "PD meter water usage (reduced due to mechanical wear of PD meter)") 3569 } 3570 else { 3571 html += formatTableDataInfo("Accuracy <br> Loss", numberWithCommas(dataItem["accuracyLost"], true), "PD meter water usage (reduced due to mechanical wear of PD meter)") 3572 } 3573 html += formatTableDataInfo("Cost Sewer", '$'+numberWithCommas(dataItem["costSewer"]),"Sewer Rate (based on 1000 gallons)") 3574 html += formatTableDataInfo("Cost Water", '$'+numberWithCommas(dataItem["costWater"]),"Water Rate (based on 1000 gals)") 3575 html += formatTableDataInfo("Total Cost", '$'+numberWithCommas(dataItem["totalCost"]),"Water rate plus sewer rate (based on 1000 gallons)") 3576 html += formatTableDataInfo("Difference", '$'+numberWithCommas(dataItem["difference"]), "Difference between 100% charge and Actual charge") 3577 html += formatTableDataInfo("Low Flow Accuracy", numberWithCommas(dataItem["lowFlowAccuracy"], true), "Additional gallons measured due to iPERL's low flow measurement capabilities") 3578 html += formatTableDataInfo("Added Revenue", '$'+numberWithCommas(dataItem["addedRevenue"]), "Additional per meter revenue as a result of the increased low flow measurement with iPERL multiplied by the total cost of water") 3579 html += formatTableDataInfo("Total Added Revenue", '$'+numberWithCommas(dataItem["totalAddedRevenue"], false), "Total added per meter revenue due to iPERL overall accuracy plus added low flow revenue") 3580 html += "</div></td></tr>" 3581 html += "</tr>" 3582 $('.ROICalculator__table-body').append(html); 3583 }); 3584 3585 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Difference", '$'+numberWithCommas(data[19]["totalDifference"]),"Difference between 100% charge and Actual charge")); 3586 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Added Revenue",'$'+ numberWithCommas(data[19]["sumAddedRevenue"]),"Additional per meter revenue as a result of the increased low flow measurement with iPERL multiplied by the total cost of water")); 3587 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Added Revenue",'$'+ numberWithCommas(data[19]["sumTotalAddedRevenue"]), "Total added per meter revenue due to iPERL overall accuracy plus added low flow revenue")); 3588 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Actual $", '$'+numberWithCommas(data[19]["sumTotalActualAmount"], true),"100% Charge field plus Added Revenue field due to iPERL's low flow capabilities multiplied by the total number of meters (Cost Sewer)")); 3589 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Billable $", '$'+numberWithCommas(data[19]["sumTotalBillableAmou
3589nt"], true),"Actual billed amount: Actual Charge multiplied by the total number of meters (Cost Sewer)")); 3590 $('.ROICalculator__savings-container-info').append(formatSavingsDataInfo("Total Additional Revenue", '$'+numberWithCommas(data[19]["totalAdditionalRevenue"], true),"Additional revenue per year as a result of iPERL's perpetual accuracy and low flow measurement capabilities")); 3591 3592 }; 3593 3594 function reset() 3595 { 3596 $('.input-message--error').remove(); 3597 $('.ROICalculator__input:not(:disabled)').each(function(){ 3598 $(this).val("") 3599 $(this).removeClass('input--error') 3600 }) 3601 $('.ROICalculator__results').hide(); 3602 $('.ROICalculator__container').removeClass('ROICalculator__container--results') 3603 if (window.innerWidth <= 992) { 3604 $('.ROICalculator__container form').show(); 3605 $('.ROICalculator__description').show() 3606 } 3607 3608 } 3609 3610 $(document).ready(function(){ 3611 setupInputFields() 3612 //Setup 3613 if($('.ROICalculator__container').length > 0) 3614 { 3615 setupPopper('quantityResidentialMeters__button', 'quantityResidentialMeters__message'); 3616 setupPopper('averageUsage__button', 'averageUsage__message'); 3617 setupPopper('twentyYearAccuracyLost__button', 'twentyYearAccuracyLost__message'); 3618 setupPopper('lowFlowAccuracyIncrease__button', 'lowFlowAccuracyIncrease__message'); 3619 setupPopper('totalAnticipatedLowFlowMeasurement__button', 'totalAnticipatedLowFlowMeasurement__message'); 3620 setupPopper('increasePerYear__button', 'increasePerYear__message'); 3621 setupPopper('waterRate__button', 'waterRate__message'); 3622 setupPopper('sewerRate__button', 'sewerRate__message'); 3623 } 3624 3625 $('.btn__reset').click(function(){ 3626 reset(); 3627 }); 3628 3629 $('.ROICalculator__back-arrow').click(function(){ 3630 $('.roicalculator').removeClass('roicalculator--fixed'); 3631 $('.ROICalculator__results').hide(); 3632 $('.ROICalculator__container form').show(); 3633 $('.ROICalculator__description').show() 3634 location.hash = '' 3635 location.hash = 'ROICalculator' 3636 3637 }); 3638 $('.ROICalculator__submit').click(function(e){ 3639 e.preventDefault() 3640 3641 $(this).removeClass('input--error') 3642 $('.ROICalculator__results').hide(); 3643 var map = {}; 3644 var failed = false; 3645 $(".ROICalculator__container input").each(function() { 3646 if($(this).next().hasClass('.input-message--error')) 3647 { 3648 $(this).next().remove(); 3649 } 3650 $(this).removeClass('.input-message--error'); 3651 var val = $(this).val().replace(',', ''); 3652 if(val && !$(this).hasClass('input--error')) 3653 { 3654 $(this).removeClass('input--error') 3655 if($(this).hasClass('ROICalculator__percent')) 3656 { 3657 val = val.replace("%", ''); 3658 val = parseFloat(val)/100; 3659 } 3660 else { 3661 val = parseFloat(val); 3662 } 3663 } 3664 else { 3665 failed = true 3666 if(!$(this).hasClass('input--error')) 3667 { 3668 $(this).addClass('input--error') 3669 $(this).after('<p class="input-message--error">This field is required.</p>'); 3670 } 3671 } 3672 map[$(this).attr("id")] = val; 3673 }); 3674 map['unit'] = $(".ROICalculator__container select").val(); 3675 3676 $('.ROICalculator__table-body').empty(); 3677 $('.ROICalculator__savings-container-info').empty(); 3678 if(!failed) 3679 { 3680 $.ajax({ 3681 type: 'GET', 3682 url: "/bin/southern/global/roicalculator", 3683 data: map, 3684 success: function (data, textStatus, jqXHR) { 3685 3686 if (window.innerWidth >= 992) { 3687 formatTableDesktop(data) 3688 $('ROICalculator__table').data('index', 1); 3689 updateTable(); 3690 $('.ROICalculator__results').show(); 3691 } 3692 else { 3693 formatTableMobile(data) 3694 $('.ROICalculator__container form').hide(); 3695 $('.ROICalculator__description').hide(); 3696 $('.ROICalculator__results').show(); 3697 $('.roicalculator').addClass('roicalculator--fixed'); 3698 $('.ROICalculator__container').addClass('ROICalculator__container--results') 3699 } 3700 //ROICalculator__table-info--header 3701 //ROICalculator__table-arrow 3702 $('.ROICalculator__table-info--header').click(function(){ 3703 $(this).next().find('td .ROICalculator__table-info--container').slideToggle(); 3704 $(this).find('.ROICalculator__table-arrow').toggleClass('ROICalculator__table-arrow--flip'); 3705 $('.ROICalculator__results').show(); 3706 }); 3707 3708 $('span[data-popover]').hover(function(){ 3709 var popoverMessage = $(this).data('popover'); 3710 $('.ROICalculator__popover-text').text(popoverMessage); 3711 $('#table__message').addClass('ROICalculator__popover-message--show'); 3712 3713 Popper.createPopper($(this)[0], $('#table__message')[0], { 3714 placement: 'bottom', 3715 modifiers: [ 3716 { 3717 name: 'offset', 3718 options: { 3719 3720 offset: [0, 15], 3721 }, 3722 }, 3723 { name: 'flip', enabled: false } 3724 ], 3725 }); 3726 3727 $(this).hasClass('popover--center') ? $('.js-popover-triangle').show() : $('.js-popover-triangle').hide(); 3728 event.stopPropagation(); 3729 }, function(){ 3730 $('#table__message').removeClass('ROICalculator__popover-message--show'); 3731 }); 3732 3733 3734 3735 }, 3736 error: function (XMLHttpRequest, textStatus, errorThrown) { 3737 $('#errordiv').val('Invalid User Name or Password'); 3738 }, 3739 }); 3740 } 3741 }); 3742 3743 3744 3745 }); 3746 3747 function updateTable() 3748 {
3749 var tableIndex = $('.ROICalculator__table').data('index'); 3750 var $tableRows = $('.ROICalculator__table-body tr'); 3751 $tableRows.hide(); 3752 $tableRows.each(function(index, row){ 3753 if(tableIndex == 1 && index < 20) 3754 { 3755 $(row).show(); 3756 } 3757 else if(tableIndex == 2 && index >= 20) 3758 { 3759 $(row).show(); 3760 } 3761 3762 }) 3763 } 3764 3765 $('.ROICalculator__table-pagination--arrow').click(function(){ 3766 var index = $('.ROICalculator__table').data('index'); 3767 var $next = $('.ROICalculator__table-pagination--next'); 3768 var $prev = $('.ROICalculator__table-pagination--previous') 3769 3770 if(index == 1) 3771 { 3772 $next.attr('disabled', true); 3773 $prev.attr('disabled', false); 3774 index = 2; 3775 } 3776 else { 3777 $next.attr('disabled', false); 3778 $prev.attr('disabled', true); 3779 index = 1; 3780 } 3781 3782 $('.ROICalculator__table').data('index' , index); 3783 3784 var text = index + " of 2"; 3785 $('.ROICalculator__table-pagination--text').text(text); 3786 updateTable(); 3787 3788 }) 3789 3790 3791}); 3792/** 3793 * Responsive Nav Bar - Hide on Scroll 3794 * 3795 * When enabled via the component dialog, the nav bar hides when the user 3796 * scrolls down and only reappears once the user scrolls all the way back 3797 * to the top of the page (scrollTop === 0). 3798 * 3799 * Requires GSAP + ScrollTrigger to be loaded globally. 3800 */ 3801 3802document.addEventListener('DOMContentLoaded', function() { 3803 var navBar = document.querySelector('.responsive-nav-bar[data-enable-hide-on-scroll="true"]'); 3804 3805 if (!navBar) { 3806 return; 3807 } 3808 3809 if (typeof gsap === 'undefined') { 3810 console.warn('Responsive Nav Bar: GSAP is not available'); 3811 return; 3812 } 3813 3814 if (typeof ScrollTrigger === 'undefined') { 3815 console.warn('Responsive Nav Bar: GSAP ScrollTrigger plugin is not available'); 3816 return; 3817 } 3818 3819 gsap.registerPlugin(ScrollTrigger); 3820 3821 var scrollContainerSelector = navBar.dataset.scrollContainerSelector || ''; 3822 var scrollContainer = null; 3823 3824 if (scrollContainerSelector.trim()) { 3825 scrollContainer = document.querySelector(scrollContainerSelector); 3826 if (!scrollContainer) { 3827 console.warn('Responsive Nav Bar: Scroll container not found for selector:', scrollContainerSelector); 3828 } 3829 } 3830 3831 var navBarHeight = navBar.offsetHeight; 3832 var isHidden = false; 3833 var refreshSuppressed = false; 3834 3835 function suppressRefresh() { 3836 if (refreshSuppressed) return; 3837 refreshSuppressed = true; 3838 ScrollTrigger.config({ autoRefreshEvents: 'none' }); 3839 } 3840 3841 function restoreRefresh() { 3842 if (!refreshSuppressed) return; 3843 refreshSuppressed = false; 3844 ScrollTrigger.config({ autoRefreshEvents: 'visibilitychange,DOMContentLoaded,load' }); 3845 } 3846 3847 function buildShowAnim(height) { 3848 var anim = gsap.timeline({ paused: true }) 3849 .from(navBar, { 3850 clipPath: 'inset(0% 0% 100% 0%)', 3851 marginBottom: -height, 3852 duration: 0.2, 3853 force3D: true, 3854 onStart: suppressRefresh, 3855 onComplete: restoreRefresh, 3856 onReverseComplete: restoreRefresh 3857 }, 0); 3858 anim.progress(1); 3859 return anim; 3860 } 3861 3862 var showAnim = buildShowAnim(navBarHeight); 3863 3864 var scrollTriggerOpts = { 3865 start: 'top top', 3866 end: 'max', 3867 onUpdate: function(self) { 3868 var scrollPosition = scrollContainer 3869 ? scrollContainer.scrollTop 3870 : window.scrollY; 3871 3872 if (!isHidden && self.direction === 1 && scrollPosition > 0) { 3873 isHidden = true; 3874 showAnim.reverse(); 3875 } else if (isHidden && scrollPosition <= 0) { 3876 isHidden = false; 3877 showAnim.play(); 3878 } 3879 } 3880 }; 3881 3882 if (scrollContainer) { 3883 scrollTriggerOpts.scroller = scrollContainer; 3884 } 3885 3886 ScrollTrigger.create(scrollTriggerOpts); 3887 3888 var resizeTimeout; 3889 window.addEventListener('resize', function() { 3890 clearTimeout(resizeTimeout); 3891 resizeTimeout = setTimeout(function() { 3892 var newNavBarHeight = navBar.offsetHeight; 3893 if (newNavBarHeight !== navBarHeight) { 3894 navBarHeight = newNavBarHeight; 3895 showAnim.kill(); 3896 showAnim = buildShowAnim(navBarHeight); 3897 } 3898 isHidden = false;
3899 showAnim.progress(1); 3900 ScrollTrigger.refresh(); 3901 }, 250); 3902 }); 3903}); 3904 3905$(function () { 3906 $(".related--article-rail[data-type='latest'], .related--article-rail[data-type='latestPublished']").each(function () { 3907 var $this = $(this); 3908 var data = $this.find('.related-article-data'); 3909 var cols = data.attr('data-columns') || ''; 3910 var root = $this; 3911 var count = parseInt(data.attr('data-count')); 3912 var hideTag = data.attr('data-hide-tag') ? data.attr('data-hide-tag') : ""; 3913 var hideImages = data.attr('data-hide-images') === 'true'; 3914 var showAuthor = data.attr('data-show-author') === 'true'; 3915 var showDescription = data.attr('data-show-description') === 'true'; 3916 if (isNaN(count)) { 3917 count = 2; 3918 } 3919 3920 if (hideImages) { 3921 root.addClass('related--article-rail--text-only'); 3922 } 3923 3924 var filter = $this.attr('data-type') === 'latestPublished' ? 'most-recent-published' : 'most-recent'; 3925 3926 $.get('/bin/southern/global/latestarticles', { 3927 tags: data.attr('data-tags'), 3928 path: data.attr('data-path'), 3929 limit: count + 1, 3930 filter: filter, 3931 start: data.attr('data-start'), 3932 }).done(function (data) { 3933 var pages = data.pages; 3934 pages = pages 3935 .filter(function (s) { 3936 return s.path != window.location.href; 3937 }) 3938 .slice(0, count); 3939 3940 for (var i = 0; i < pages.length; i++) { 3941 var tagClasses = pages[i].tagClasses ? pages[i].tagClasses : ""; 3942 var article = $( 3943 '<div class="related--article-list col-md-12 ' + cols + ' ' + tagClasses + '">' 3944 ); 3945 3946 var redirect = pages[i].customRedirect ? pages[i].customRedirect : pages[i].path; 3947 3948 if (!hideImages) { 3949 var imageAlt = escapeHtmlAttr(pages[i].articlePromoImageAlt); 3950 article.append( 3951 '<div class="related--article-image-section" ><span class="related--article-tag '+hideTag+'">' + 3952 pages[i].primaryTag + 3953 '</span> <a href="' + 3954 redirect + 3955 '"> <img class="related--article-image sr-only" src="' + 3956 pages[i].articlePromoImage + 3957 '" alt="' + 3958 imageAlt + 3959 '"/><figure class="fixedratio" style="background-image: url(' + 3960 pages[i].articlePromoImage + 3961 ');"></figure></a></div>' 3962 ); 3963 } else { 3964 article.append( 3965 '<span class="related--article-tag ' + hideTag + ' related--article-tag--standalone">' + 3966 pages[i].primaryTag + 3967 '</span>' 3968 ); 3969 } 3970 3971 article.append( 3972 $('<a class="related--article-desc"></a>').attr('href', redirect).text(pages[i].title) 3973 ); 3974 3975 if (showDescription && pages[i].description) { 3976 article.append( 3977 $('<p class="related--article-description"></p>').text(pages[i].description) 3978 ); 3979 } 3980 3981 if (showAuthor && pages[i].author) { 3982 var $author = $('<div class="related--article-author"></div>'); 3983 if (pages[i].authorLink) { 3984 $author.append( 3985 $('<a class="related--article-author-link"></a>') 3986 .attr('href', pages[i].authorLink) 3987 .attr('aria-label', pages[i].author) 3988 .text(pages[i].author) 3989 ); 3990 } else { 3991 $author.text(pages[i].author); 3992 } 3993 article.append($author); 3994 } 3995 3996 root.append(article); 3997 } 3998 }); 3999 }); 4000 4001 function escapeHtmlAttr(value) { 4002 if (value == null || value === '') { 4003 return ''; 4004 } 4005 return String(value) 4006 .replace(/&/g, '&') 4007 .replace(/"/g, '"') 4008 .replace(/'/g, ''') 4009 .replace(/</g, '<') 4010 .replace(/>/g, '>'); 4011 } 4012}); 4013 4014$(function () { 4015 var categoryOption = '', 4016 subcategoryOption = '', 4017 currentScreenSize = 'mobile'; 4018 4019 // initial check of screensize 4020 if (window.innerWidth >= 992) { 4021 currentScreenSize = 'desktop'; 4022 } 4023 4024 var mq = window.matchMedia('(min-width: 992px)'); 4025 mq.addEventListener('change', WidthChange); 4026 WidthChange(mq); 4027 4028 // media query change 4029 function WidthChange(mq) { 4030 $('.rebates__filter.category select').each(function (ev, obj) { 4031 var $el = $(this), 4032 option = $(this).find('option:selected').data('option'), 4033 subCategory = $(this) 4034 .closest('.rebates__filter') 4035 .find('.subcategory.active li[data-active]') 4036 .data('option'); 4037 if (option && option != 'All Categories') { 4038 option = option 4039 .replace(/:|\//gi, function () { 4040 return '-'; 4041 }) 4042 .toLowerCase(); 4043 } 4044 if (subCategory) { 4045 subCategory = subCategory 4046 .replace(/:|\//gi, function () { 4047 return '-'; 4048 }) 4049 .toLowerCase(); 4050 } 4051 4052 if (mq.matches) { 4053 // window width is at least 992px 4054 currentScreenSize = 'desktop'; 4055 filterRebateResults($el, option, subCategory); 4056 } else { 4057 // window width is less than 992px 4058 currentScreenSize = 'mobile'; 4059 filterRebateResults($el, option, subCategory); 4060 } 4061 }); 4062 } 4063 4064 $('.rebates__filter.category select').bind({ 4065 changed: function (ev, obj) { 4066 var $el = $(this), 4067 option = obj.dropdownOption.data('option'); 4068 filterRebateResults($el, option); 4069 showSubcategorySelect($el, option); 4070 }, 4071 }); 4072 $('.rebates__filter.subcategory select').bind({ 4073 changed: function (ev, obj) { 4074 var $el = $(this), 4075 option = obj.dropdownOption.data('option'); 4076 filterRebateResults($el, categoryOption, option); 4077 }, 4078 }); 4079 $('.rebates__filter select').selectBoxIt(); 4080 4081 function filterRebateResults($el, category, subcategory) { 4082 // track category, subcategory 4083 categoryOption = category; 4084 subcategoryOption = subcategory; 4085 4086 var rebatesComponent = $el.closest('.rebatesgrid'); 4087 4088 // filter grid by category, subcategory 4089 var $gridItems = rebatesComponent.find('.rebates__col'); 4090 var itemsToRemove = []; 4091 4092 $gridItems.each(function (i) { 4093 $(this).show(); 4094 var tags = $(this).attr('data-tags'); 4095 if (tags) { 4096 tags = tags.toLowerCase().split(';'); 4097 } 4098 if (categoryOption != 'All Categories') { 4099 if (categoryOption != '' && categoryOption != undefined) { 4100 categoryOption = category 4101 .replace(/:|\//gi, function () { 4102 return '-'; 4103 }) 4104 .toLowerCase(); 4105 4106 var subcategoryOptionMatches = tags.filter(function (s) { 4107 return s.includes(categoryOption); 4108 }); 4109 if ( 4110 tags && 4111 tags.indexOf(categoryOption) < 0 && 4112 subcategoryOptionMatches.length <= 0 4113 ) { 4114 $(this).hide(); 4115 itemsToRemove.push($(this)); 4116 } else { 4117 $(this).show(); 4118 } 4119 } 4120 if ( 4121 subcategoryOption != '' && 4122 subcategoryOption != undefined && 4123 subcategoryOption != 'All Categories' 4124 ) { 4125 subcategoryOption = subcategory 4126 .replace(/:|\//gi, function (x) { 4127 return '-'; 4128 }) 4129 .toLowerCase(); 4130 if (tags && tags.indexOf(subcategoryOption) < 0) { 4131 $(this).hide(); 4132 if (itemsToRemove.indexOf(i) < 0) { 4133 itemsToRemove.push($(this)); 4134 } 4135 } else { 4136 if (tags && tags.indexOf(categoryOption) > -1) { 4137 $(this).show(); 4138 } 4139 } 4140 } 4141 } 4142 }); 4143 4144 // if slider, don't pagninate 4145 $slider = rebatesComponent.find('.pagination'); 4146 if ($slider) { 4147 // remove items from array before initializing pagination 4148 for (var i = 0; i < itemsToRemove.length; i++) { 4149 for (var j = 0; j < $gridItems.length; j++) { 4150 if ($($gridItems[j]).is(itemsToRemove[i])) { 4151 $gridItems.splice(j, 1); 4152 j--; 4153 } 4154 } 4155 } 4156 4157 // handle pagination 4158 var $pagination = rebatesComponent.find('.rebates__pagination'); 4159 var $visibleItems = rebatesComponent.find('.rebates__pages'); 4160 var rowsDesktop = rebatesComponent 4161 .find('[data-rows-desktop]') 4162 .attr('data-rows-desktop'); 4163 var rowsMobile = rebatesComponent 4164 .find('[data-rows-mobile]') 4165 .attr('data-rows-mobile'); 4166 if (currentScreenSize == 'desktop') { 4167 initPagination($pagination, $gridItems, 3 * rowsDesktop, $visibleItems); 4168 } else { 4169 initPagination($pagination, $gridItems, 2 * rowsMobile, $visibleItems); 4170 } 4171 } 4172 } 4173 4174 function showSubcategorySelect($el, category) { 4175 var rebatesComponent = $el.closest('.rebatesgrid'); 4176 4177 rebatesComponent 4178 .find('.rebates__filter.subcategory.active') 4179 .removeClass('active'); 4180 if (category != '') { 4181 var subcategoryFilter = rebatesComponent.find( 4182 '.rebates__filter.subcategory[data-category="' + category + '"]' 4183 ); 4184 subcategoryFilter.addClass('active'); 4185 var categorySelect = rebatesComponent 4186 .find('.rebates__filter.category') 4187 .find('select') 4188 .data('selectBox-selectBoxIt'); 4189 var subcategorySelect = subcategoryFilter 4190 .find('select') 4191 .data('selectBox-selectBoxIt'); 4192 if (subcategorySelect) { 4193 var firstOption = subcategorySelect.selectOption(0); 4194 } 4195 } 4196 } 4197}); 4198 4199$(function() { 4200 // initiate polyfill 4201 $(document).ready(function() { 4202 if (document.querySelector('.rebates__img img')) { 4203 var objectFit = new ObjectFitPolyfill('.rebates__img img'); 4204 } 4205 }); 4206}); 4207 4208(function() { 4209 'use strict'; 4210 4211 document.addEventListener('DOMContentLoaded', function() { 4212 initializePricingRows(); 4213 }); 4214 4215 function resolveTemplateSimple(templateStr, data) { 4216 return templateStr.replace(/\{\{([^}]+)\}\}/g, function(match, expression) { 4217 var parts = expression.trim().split('.'); 4218 var value = data; 4219 for (var i = 0; i < parts.length; i++) { 4220 if (value == null) return ''; 4221 var key = parts[i].replace(/^\[|\]$/g, ''); 4222 value = value[key]; 4223 } 4224 return (value != null) ? value : ''; 4225 }); 4226 } 4227 4228 function compileAndRender(templateStr, data) { 4229 if (typeof Handlebars !== 'undefined') { 4230 try { 4231 var template = Handlebars.compile(templateStr); 4232 return template(data); 4233 } catch (e) { 4234 console.warn('[RatePlans] Handlebars.compile failed (possible CSP restriction):', e.message); 4235 } 4236 } else { 4237 console.warn('[RatePlans] Handlebars not available, using fallback resolver'); 4238 } 4239 return resolveTemplateSimple(templateStr, data); 4240 } 4241 4242 function initializePricingRows() { 4243 var pricingRows = document.querySelectorAll('.pricing-row .cf-row[data-cfpath]'); 4244 4245 pricingRows.forEach(function(row) { 4246 var cfPath = row.getAttribute('data-cfpath'); 4247 var variation = row.getAttribute('data-variation'); 4248 4249 if (cfPath) { 4250 row.classList.add('loading'); 4251 var jsonUrl = buildJsonUrl(cfPath, variation); 4252 fetchAndRenderRow(row, jsonUrl); 4253 } 4254 }); 4255 } 4256 4257 function buildJsonUrl(cfPath, variation) { 4258 cfPath = cfPath.replace(/\/+$/, ''); 4259 var jsonUrl = cfPath + '.model.json'; 4260 if (!jsonUrl.startsWith('/')) { 4261 jsonUrl = '/' + jsonUrl; 4262 } 4263 return buildAbsoluteUrl(jsonUrl); 4264 } 4265 4266 function buildAbsoluteUrl(path) { 4267 if (path.startsWith('http://') || path.startsWith('https://')) { 4268 return path; 4269 } 4270 return window.location.origin + path; 4271 } 4272 4273 function fetchAndRenderRow(rowElement, jsonUrl) { 4274 fetch(jsonUrl, { 4275 credentials: 'same-origin', 4276 headers: { 4277 'Accept': 'application/json' 4278 } 4279 }) 4280 .then(function(response) { 4281 if (!response.ok) { 4282 throw new Error('HTTP ' + response.status + ': ' + jsonUrl); 4283 } 4284 return response.text(); 4285 }) 4286 .then(function(text) { 4287 if (!text || text.trim().length === 0) { 4288 throw new Error('Empty response from: ' + jsonUrl); 4289 } 4290 try { 4291 return JSON.parse(text); 4292 } catch (e) { 4293 throw new Error('Invalid JSON response from: ' + jsonUrl); 4294 } 4295 }) 4296 .then(function(data) { 4297 if (data.error) { 4298 throw new Error('Server error: ' + data.error); 4299 } 4300 renderRowWithData(rowElement, data); 4301 rowElement.classList.remove('loading'); 4302 rowElement.classList.add('loaded'); 4303 }) 4304 .catch(function(error) { 4305 console.error('[RatePlans] Error fetching content fragment data:', error); 4306 rowElement.classList.remove('loading'); 4307 rowElement.classList.add('error'); 4308 }); 4309 } 4310 4311 function renderRowWithData(rowElement, data) { 4312 var cells = rowElement.querySelectorAll('div'); 4313
4314 cells.forEach(function(cell) { 4315 var fieldName = cell.getAttribute('data-template-field'); 4316 var templateSource = fieldName ? (data[fieldName] || '') : cell.innerHTML; 4317 4318 if (templateSource && templateSource.includes('{{')) { 4319 var renderedHtml = compileAndRender(templateSource, data); 4320 cell.innerHTML = renderedHtml; 4321 } 4322 }); 4323 } 4324 4325})(); 4326 4327document.addEventListener('DOMContentLoaded', () => { 4328 const stickyParents = document.querySelectorAll('.sticky-row'); 4329 4330 stickyParents.forEach((stickyParent) => { 4331 // Create a "sentinel" or observer target above each sticky element 4332 const sentinel = document.createElement('div'); 4333 sentinel.style.position = 'absolute'; 4334 sentinel.style.top = '-1px'; // Place it just above 4335 sentinel.style.height = '1px'; 4336 sentinel.style.width = '100%'; 4337 stickyParent.parentNode.insertBefore(sentinel, stickyParent); 4338 4339 const observer = new IntersectionObserver(([entry]) => { 4340 // If the sentinel is intersecting, the parent is NOT stuck at the top 4341 if (entry.isIntersecting) { 4342 stickyParent.classList.remove('is-stuck'); 4343 } else { 4344 // The sentinel is out of view (above the viewport top), so it is stuck 4345 stickyParent.classList.add('is-stuck'); 4346 } 4347 }, { 4348 root: null, // observe against the viewport 4349 rootMargin: '0px', 4350 threshold: 0 // Fire as soon as 1 pixel crosses the boundary 4351 }); 4352 4353 observer.observe(sentinel); 4354 }); 4355}); 4356$(function() { 4357 var SCREEN_MD = 992, 4358 prevMd, 4359 imgSelector = '.quote-container .quote__img img'; 4360 4361 function removePolyFillStyles() { 4362 var quoteImgs = document.querySelectorAll(imgSelector); 4363 for (var i = 0; i < quoteImgs.length; i++) { 4364 quoteImgs[i].removeAttribute('style'); 4365 quoteImgs[i].classList.remove('object-fit'); 4366 } 4367 } 4368 4369 function quoteImagePolyfill() { 4370 var md = window.matchMedia('(min-width: ' + SCREEN_MD + 'px)').matches; 4371 // if the window width changed between sm and md || the document initialized (since prevMd wasn't set on init) 4372 if (md != prevMd || !prevMd) { 4373 if (md) { 4374 if (document.querySelectorAll(imgSelector)) { 4375 var objectFit = new ObjectFitPolyfill(imgSelector); 4376 } 4377 } else { 4378 removePolyFillStyles(); 4379 } 4380 4381 prevMd = md; 4382 } 4383 } 4384 4385 // initiate polyfill 4386 $(window).on('load', function() { 4387 quoteImagePolyfill(); 4388 }); 4389 4390 // add or remove object-fit polyfill when screen is resized above or below 992px 4391 window.addEventListener('resize', quoteImagePolyfill); 4392}); 4393 4394document.addEventListener('DOMContentLoaded', () => { 4395 const allQuestionnaires = document.querySelectorAll('.questionnaire-wrapper'); 4396 allQuestionnaires.forEach((questionnaireWrapper) => { 4397 const componentId = questionnaireWrapper.id; 4398 const container = questionnaireWrapper.closest('.questionnaire-container'); 4399 if (!container) return; 4400 var simulatedSelections = ""; 4401 const panels = container.querySelectorAll('.panel'); 4402 const prevBtn = container.querySelector('.prev-button button'); 4403 const nextBtn = container.querySelector('.next-button button'); 4404 const resultsContainer = container.querySelector('.questionnaire-results'); 4405 const ctaWrapper = container.querySelector('.cta-wrapper'); 4406 const resetBtn = ctaWrapper?.querySelector('button.reset-cta'); 4407 const progressSteps = container.querySelectorAll('.progress-section li'); 4408 const progressBar = container.querySelector('.progress-section'); 4409 const cardsContainer = container.querySelector('.cards-container'); 4410 const cardWrappers = Array.from( 4411 cardsContainer?.querySelectorAll('.card-wrapper') || [] 4412 ); 4413 const mobilePrevBtn = cardsContainer?.querySelector( 4414 '.mobile-prev-button button' 4415 ); 4416 const mobileNextBtn = cardsContainer?.querySelector( 4417 '.mobile-next-button button' 4418 ); 4419 const emailButton = container.querySelectorAll('.email-button'); 4420 4421 if (emailButton) { 4422 $(emailButton).click(function () { 4423 $('.email-submit--background').show(); 4424 $('.email-submit').show(); 4425 }); 4426 4427 $('.email__close').click(function () { 4428 $('.email-submit--background').hide(); 4429 $('.email-submit').hide(); 4430 }); 4431 4432 function resetMessage() { 4433 $('.input-message--error').remove(); 4434 $('.input--error').removeClass('input--error'); 4435 } 4436 4437 function isValidEmail(email) { 4438 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); 4439 } 4440 4441 $('.send-email-js-questionnaire-trigger').click(function () { 4442 var email = $(container.querySelectorAll('.questionnaire-email')).val(); 4443 console.log(email); 4444 console.log(convertUserSelections(getUserSelections())); 4445 var path = $(this).data('path'); 4446 resetMessage(); 4447 if (!email || !isValidEmail(email)) { 4448 $(this).addClass('input--error'); 4449 $(this) 4450 .prev() 4451 .before( 4452 `<p class='input-message--error'>Please enter a valid email address.</p>` 4453 ); 4454 } else { 4455 $.ajax({ 4456 type: 'POST', 4457 url: '/bin/southern/global/questionnaireemail', 4458 data: { 4459 email: email, 4460 path: path, 4461 url: window.location.href, 4462 share: simulatedSelections != "" ? simulatedSelections : convertUserSelections(getUserSelections()), 4463 }, 4464 success: function (msg) { 4465 $('.email-submit--background').hide(); 4466 $('.email-submit').hide(); 4467 }, 4468 error: function () { 4469 $(this).addClass('input--error'); 4470 $(this) 4471 .prev() 4472 .before( 4473 "<p class='input-message--error'>There was an issue sending the email. Please try again later.</p>" 4474 ); 4475 }, 4476 }); 4477 } 4478 }); 4479 } 4480
4481 let currentPanelIndex = 0; 4482 let currentCardIndex = 0; 4483 let visibleCards = []; 4484 4485 const customResultsParam = getQueryParam('custom-results'); 4486 console.log(customResultsParam); 4487 if (customResultsParam) { 4488 simulatedSelections = parseCustomResultsString(customResultsParam); 4489 4490 // Skip to results view 4491 questionnaireWrapper.classList.add('hidden'); 4492 progressBar.classList.add('hidden'); 4493 resultsContainer.classList.remove('hidden'); 4494 ctaWrapper?.classList.remove('hidden'); 4495 evaluateResultsVisibility(simulatedSelections); 4496 } 4497 if (!customResultsParam) { 4498 console.log('test'); 4499 questionnaireWrapper.classList.remove('hidden'); 4500 updatePanelVisibility(); 4501 updateQuestionDependencies(); 4502 } 4503 4504 function evaluateRangeCondition(userValue, condition) { 4505 if (typeof userValue !== 'number' || isNaN(userValue)) return false; 4506 if (condition.includes('-')) { 4507 const [min, max] = condition.split('-').map(Number); 4508 return userValue >= min && userValue <= max; 4509 } 4510 if (condition.startsWith('<=')) 4511 return userValue <= parseFloat(condition.substring(2)); 4512 if (condition.startsWith('>=')) 4513 return userValue >= parseFloat(condition.substring(2)); 4514 if (condition.startsWith('<')) 4515 return userValue < parseFloat(condition.substring(1)); 4516 if (condition.startsWith('>')) 4517 return userValue > parseFloat(condition.substring(1)); 4518 if (condition.startsWith('=')) 4519 return userValue === parseFloat(condition.substring(1)); 4520 return false; 4521 } 4522 4523 function convertUserSelections(selections) { 4524 const parts = []; 4525 4526 Object.entries(selections).forEach(([key, value]) => { 4527 if (Array.isArray(value)) { 4528 value.forEach((val) => { 4529 parts.push(`${encodeURIComponent(key)}${encodeURIComponent(val)}`); 4530 }); 4531 } else { 4532 parts.push(`${encodeURIComponent(key)}${encodeURIComponent(value)}`); 4533 } 4534 }); 4535 4536 return parts.join('-'); 4537 } 4538 4539 function getUserSelections() { 4540 const selections = {}; 4541 container.querySelectorAll('.question').forEach((question, index) => { 4542 const selectedRadio = question.querySelector( 4543 'input[type="radio"]:checked' 4544 ); 4545 const selectedCheckboxes = question.querySelectorAll( 4546 'input[type="checkbox"]:checked' 4547 ); 4548 const selectedDropdown = question.querySelector('select'); 4549 const selectedInput = question.querySelector( 4550 'input[type="text"][data-options], input[type="number"][data-options]' 4551 ); 4552 const simpleInput = question.querySelector( 4553 'input[type="text"]:not([data-options]), input[type="number"]:not([data-options])' 4554 ); 4555 4556 if (selectedRadio) { 4557 selections[index + 1] = selectedRadio.value; 4558 } else if (selectedCheckboxes.length > 0) { 4559 selections[index + 1] = Array.from(selectedCheckboxes).map( 4560 (cb) => cb.value 4561 ); 4562 } else if (selectedDropdown && selectedDropdown.value !== '') { 4563 selections[index + 1] = selectedDropdown.value; 4564 } else if (selectedInput && selectedInput.value.trim() !== '') { 4565 const userValue = parseFloat(selectedInput.value.trim()); 4566 const optionsData = selectedInput.dataset.options; 4567 let matchedOptions = []; 4568 if (optionsData) { 4569 try { 4570 const decodedOptions = optionsData.replace(/"/g, '"'); 4571 const options = JSON.parse(decodedOptions); 4572 options.forEach((option) => { 4573 if (evaluateRangeCondition(userValue, option.optionContent)) { 4574 matchedOptions.push(option.optionCode); 4575 } 4576 }); 4577 } catch (e) { 4578 console.error('Invalid range options JSON:', optionsData, e); 4579 } 4580 } 4581 if (matchedOptions.length > 0) { 4582 selections[index + 1] = matchedOptions; 4583 } 4584 } else if (simpleInput && simpleInput.value.trim() !== '') { 4585 selections[index + 1] = simpleInput.value.trim(); 4586 } 4587 }); 4588 return selections; 4589 } 4590 4591 function parseDependencyString(depString) {
4592 try { 4593 if (depString === '[]' || depString === '') return []; 4594 if (depString.startsWith('[')) { 4595 depString = depString.replace(/"/g, '"'); 4596 return JSON.parse(depString); 4597 } 4598 return [depString]; 4599 } catch (e) { 4600 console.error('Invalid dependency format:', depString, e); 4601 return []; 4602 } 4603 } 4604 4605 function parseConditionBlock(block) { 4606 return block 4607 .split('-') 4608 .map((c) => { 4609 const match = c.trim().match(/^(\d+)([A-Za-z0-9]+)$/); 4610 if (!match) return null; 4611 return { 4612 questionNum: parseInt(match[1], 10), 4613 expectedVal: match[2], 4614 }; 4615 }) 4616 .filter(Boolean); 4617 } 4618 4619 function matchesDependency(dependencyString, selections) { 4620 const groups = parseDependencyString(dependencyString); 4621 if (groups.length === 0) return true; 4622 return groups.some((group) => { 4623 if (typeof group !== 'string') return false; 4624 const orBlocks = group.split('||').map((b) => b.trim()); 4625 return orBlocks.some((block) => { 4626 const conditions = parseConditionBlock(block); 4627 return conditions.every(({ questionNum, expectedVal }) => { 4628 const userAnswer = selections[questionNum]; 4629 return Array.isArray(userAnswer) 4630 ? userAnswer.includes(expectedVal) 4631 : userAnswer === expectedVal; 4632 }); 4633 }); 4634 }); 4635 } 4636 4637 function updateQuestionDependencies() { 4638 const allQuestions = Array.from( 4639 container.querySelectorAll('.question-wrapper') 4640 ); 4641 const selections = getUserSelections(); 4642 allQuestions.forEach((wrapper) => { 4643 const question = wrapper.querySelector('.question'); 4644 const dependency = question?.dataset.dependency; 4645 if (!dependency || matchesDependency(dependency, selections)) { 4646 wrapper.classList.remove('hidden'); 4647 } else { 4648 wrapper.classList.add('hidden'); 4649 wrapper 4650 .querySelectorAll('input') 4651 ?.forEach((input) => (input.checked = false)); 4652 const select = wrapper.querySelector('select'); 4653 if (select) select.selectedIndex = 0; 4654 } 4655 }); 4656 updateButtonStates(); 4657 } 4658 4659 function updatePanelVisibility() { 4660 panels.forEach((panel, index) => { 4661 panel.classList.toggle('hidden', index !== currentPanelIndex); 4662 }); 4663 resultsContainer.classList.add('hidden'); 4664 updateButtonStates(); 4665 updateProgressBar(); 4666 } 4667 4668 function updateButtonStates() { 4669 prevBtn.disabled = currentPanelIndex === 0; 4670 prevBtn.classList.toggle('disabled', currentPanelIndex === 0); 4671 4672 const currentPanel = panels[currentPanelIndex]; 4673 const visibleQuestions = currentPanel.querySelectorAll( 4674 '.question-wrapper:not(.hidden) .question' 4675 ); 4676 4677 const allAnswered = Array.from(visibleQuestions).every((question) => { 4678 if (question.classList.contains('radio')) { 4679 return Array.from( 4680 question.querySelectorAll('input[type="radio"]') 4681 ).some((r) => r.checked); 4682 } else if (question.classList.contains('checkboxes')) { 4683 return Array.from( 4684 question.querySelectorAll('input[type="checkbox"]') 4685 ).some((c) => c.checked); 4686 } else if (question.classList.contains('dropdown')) { 4687 const select = question.querySelector('select'); 4688 return select && select.value !== ''; 4689 } else if (question.classList.contains('range')) { 4690 const input = question.querySelector( 4691 'input[type="text"], input[type="number"]' 4692 ); 4693 return input && input.value.trim() !== ''; 4694 } 4695 return true; 4696 }); 4697 4698 nextBtn.disabled = !allAnswered; 4699 nextBtn.classList.toggle('disabled', !allAnswered); 4700 } 4701 4702 function evaluateResultsVisibility(simulatedSelections) { 4703 var selections; 4704 if (simulatedSelections == undefined) { 4705 selections = getUserSelections(); 4706 } else { 4707 selections = simulatedSelections; 4708 } 4709 4710 visibleCards = []; 4711
4712 cardWrappers.forEach((el) => { 4713 const dependency = el.getAttribute('data-dependency'); 4714 const isVisible = 4715 !dependency || matchesDependency(dependency, selections); 4716 el.style.display = isVisible ? '' : 'none'; 4717 if (isVisible) visibleCards.push(el); 4718 }); 4719 4720 container.querySelectorAll('.addon-wrapper').forEach((el) => { 4721 const dependency = el.getAttribute('data-dependency'); 4722 el.style.display = 4723 !dependency || matchesDependency(dependency, selections) 4724 ? '' 4725 : 'none'; 4726 }); 4727 4728 updateCardCarousel(); 4729 updateResultText('.cards-result-text', '.card-wrapper'); 4730 updateResultText('.addons-result-text', '.addon-wrapper'); 4731 } 4732 4733 function updateProgressBar() { 4734 progressSteps.forEach((step, index) => { 4735 step.classList.remove('is-active', 'is-complete'); 4736 if (index < currentPanelIndex) step.classList.add('is-complete'); 4737 else if (index === currentPanelIndex) step.classList.add('is-active'); 4738 }); 4739 } 4740 4741 function updateCardCarousel() { 4742 const isMobile = window.matchMedia('(max-width: 991px)').matches; 4743 if (mobileNextBtn && mobilePrevBtn) { 4744 mobilePrevBtn.classList.toggle('hidden', visibleCards.length < 2); 4745 mobileNextBtn.classList.toggle('hidden', visibleCards.length < 2); 4746 } 4747 if (isMobile && visibleCards.length > 1) { 4748 cardsContainer?.classList.add('carousel'); 4749 currentCardIndex = 0; 4750 updateCardVisibility(); 4751 } else { 4752 cardsContainer?.classList.remove('carousel'); 4753 visibleCards.forEach((card) => (card.style.display = '')); 4754 } 4755 } 4756 4757 function updateCardVisibility() { 4758 visibleCards.forEach((card, index) => { 4759 card.style.display = index === currentCardIndex ? '' : 'none'; 4760 }); 4761 if (mobilePrevBtn) { 4762 mobilePrevBtn.disabled = currentCardIndex === 0; 4763 mobilePrevBtn.classList.toggle('disabled', currentCardIndex === 0); 4764 } 4765 if (mobileNextBtn) { 4766 mobileNextBtn.disabled = currentCardIndex === visibleCards.length - 1; 4767 mobileNextBtn.classList.toggle( 4768 'disabled', 4769 currentCardIndex === visibleCards.length - 1 4770 ); 4771 } 4772 } 4773 4774 function goToNextPanel() { 4775 if (!nextBtn.disabled && currentPanelIndex < panels.length - 1) { 4776 currentPanelIndex++; 4777 updatePanelVisibility(); 4778 scrollToProgressBar(); 4779 } else if (!nextBtn.disabled) { 4780 questionnaireWrapper.classList.add('hidden'); 4781 progressBar.classList.add('hidden'); 4782 resultsContainer.classList.remove('hidden'); 4783 ctaWrapper?.classList.remove('hidden'); 4784 evaluateResultsVisibility(); 4785 } 4786 } 4787 4788 function goToPrevPanel() { 4789 if (currentPanelIndex > 0) { 4790 currentPanelIndex--; 4791 updatePanelVisibility(); 4792 scrollToProgressBar(); 4793 } 4794 } 4795 4796 function resetQuestionnaire() { 4797 currentPanelIndex = 0; 4798 currentCardIndex = 0; 4799 visibleCards = []; 4800 simulatedSelections = ""; 4801 4802 container.querySelectorAll('input').forEach((input) => { 4803 if (input.type === 'radio' || input.type === 'checkbox') { 4804 input.checked = false; 4805 } else if (input.type === 'text' || input.type === 'number') { 4806 input.value = ''; 4807 } 4808 }); 4809 4810 container.querySelectorAll('select').forEach((select) => { 4811 select.selectedIndex = 0; 4812 }); 4813 4814 container.querySelectorAll('.question-wrapper').forEach((q) => { 4815 q.classList.remove('hidden'); 4816 }); 4817 4818 progressSteps.forEach((li) => { 4819 li.classList.remove('is-active', 'is-complete'); 4820 }); 4821 4822 questionnaireWrapper.classList.remove('hidden'); 4823 progressBar.classList.remove('hidden'); 4824 resultsContainer.classList.add('hidden'); 4825 ctaWrapper?.classList.add('hidden'); 4826 4827 updatePanelVisibility(); 4828 updateQuestionDependencies(); 4829 evaluateResultsVisibility(); 4830 scrollToProgressBar(); 4831 } 4832 4833 prevBtn?.addEventListener('click', goToPrevPanel); 4834 nextBtn?.addEventListener('click', goToNextPanel); 4835 4836 container 4837 .querySelectorAll( 4838 'input[type="radio"], input[type="checkbox"], select, input[type="text"], input[type="number"]' 4839 ) 4840 .forEach((input) => { 4841 input.addEventListener('input', () => { 4842 updateQuestionDependencies(); 4843 updateButtonStates(); 4844 }); 4845 }); 4846 4847 resetBtn?.addEventListener('click', resetQuestionnaire); 4848 mobileNextBtn?.addEventListener('click', () => { 4849 if (currentCardIndex < visibleCards.length - 1) { 4850 currentCardIndex++; 4851 updateCardVisibility(); 4852 } 4853 }); 4854 mobilePrevBtn?.addEventListener('click', () => { 4855 if (currentCardIndex > 0) { 4856 currentCardIndex--; 4857 updateCardVisibility(); 4858 } 4859 }); 4860 4861 function scrollToProgressBar() { 4862 const progressSection = container.querySelector('.progress-section'); 4863 if (progressSection) { 4864 const rect = progressSection.getBoundingClientRect(); 4865 const scrollTop = 4866 window.pageYOffset || document.documentElement.scrollTop; 4867 const targetY = rect.top + scrollTop - 100; 4868 window.scrollTo({ top: targetY, behavior: 'smooth' }); 4869 } 4870 } 4871 4872 function updateResultText(containerSelector, itemSelector) { 4873 const container = document.querySelector(containerSelector); 4874 const allItems = document.querySelectorAll(itemSelector); 4875 const visibleItems = Array.from(allItems).filter( 4876 (el) => el.style.display !== 'none' 4877 ); 4878 4879 // First check: if there are NO items at all (even hidden) 4880 if (allItems.length === 0) { 4881 container?.querySelectorAll('[data-results]').forEach((el) => { 4882 el.style.display = 'none'; 4883 }); 4884 return; // Done early 4885 } 4886 4887 const visibleCount = visibleItems.length; 4888 4889 container?.querySelectorAll('[data-results]').forEach((el) => { 4890 const condition = el.getAttribute('data-results'); 4891 if (condition === '0' && visibleCount === 0) { 4892 el.style.display = ''; 4893 } else if (condition === '1' && visibleCount === 1) { 4894 el.style.display = ''; 4895 } else if (condition === '>1' && visibleCount > 1) { 4896 el.style.display = ''; 4897 } else { 4898 el.style.display = 'none'; 4899 } 4900 }); 4901 } 4902 4903 window.addEventListener('resize', updateCardCarousel); 4904 }); 4905 4906 function getQueryParam(param) {
4907 const urlParams = new URLSearchParams(window.location.search); 4908 return urlParams.get(param); 4909 } 4910 4911 function parseCustomResultsString(resultString) { 4912 const selections = {}; 4913 const pairs = resultString.split('-'); 4914 4915 pairs.forEach((pair) => { 4916 const match = pair.match(/^(\d+)(.+)$/); 4917 if (match) { 4918 const [_, questionNum, answer] = match; 4919 if (!selections[questionNum]) { 4920 selections[questionNum] = answer; 4921 } else if (Array.isArray(selections[questionNum])) { 4922 selections[questionNum].push(answer); 4923 } else { 4924 selections[questionNum] = [selections[questionNum], answer]; 4925 } 4926 } 4927 }); 4928 4929 return selections; 4930 } 4931}); 4932 4933$(function() { 4934 var SCREEN_MD = 992, 4935 prevMd, 4936 imgSelector = '.feature-story__container:not( .feature-story--avoid-polyfill) img'; 4937 4938 function removePolyFillStyles() { 4939 var featureStoryImgs = document.querySelectorAll(imgSelector); 4940 for (var i = 0; i < featureStoryImgs.length; i++) { 4941 featureStoryImgs[i].removeAttribute('style'); 4942 featureStoryImgs[i].classList.remove('object-fit'); 4943 } 4944 } 4945 4946 function featureStoryImagePolyfill() { 4947 var md = window.matchMedia('(min-width: ' + SCREEN_MD + 'px)').matches; 4948 // if the window width changed between sm and md || the document initialized (since prevMd wasn't set on init) 4949 if (md != prevMd || !prevMd) { 4950 if (md) { 4951 if (document.querySelectorAll(imgSelector)) { 4952 var objectFit = new ObjectFitPolyfill(imgSelector); 4953 } 4954 } else { 4955 removePolyFillStyles(); 4956 } 4957 4958 prevMd = md; 4959 } 4960 } 4961 4962 // initiate polyfill 4963 $(window).on('load', function() { 4964 featureStoryImagePolyfill(); 4965 }); 4966 4967 // add or remove object-fit polyfill when screen is resized above or below 992px 4968 window.addEventListener('resize', featureStoryImagePolyfill); 4969}); 4970 4971(function() { 4972 var SCREEN_MD = 992, 4973 prevMd, 4974 promoCarouselSwipers = []; // Store multiple swiper instances 4975 4976 //custom function 4977 function initPromoCarouselSwiper() { 4978 var md = window.matchMedia('(min-width: ' + SCREEN_MD + 'px)').matches; 4979 if (md != prevMd || !prevMd) { 4980 if (md && promoCarouselSwipers.length > 0) { 4981 // destroy the swiper on desktop (992px and up) 4982 $('.promo-carousel').each(function(index) { 4983 if (promoCarouselSwipers[index] != undefined) { 4984 promoCarouselSwipers[index].destroy(true, true); 4985 promoCarouselSwipers[index] = undefined; 4986 } 4987 }); 4988 } else if (!md && promoCarouselSwipers.length === 0) { 4989 // initialize the swiper on mobile 4990 $('.promo-carousel').each(function(index) { 4991 var $this = $(this); 4992 promoCarouselSwipers[index] = new Swiper($this[0], { 4993 speed: 400, 4994 spaceBetween: 20, 4995 slidesPerView: 3, 4996 grabCursor: true, 4997 pagination: { 4998 el: $this.children('.swiper-pagination')[0], 4999 type: 'bullets', 5000 clickable: true 5001 }, 5002 breakpoints: { 5003 // when window width is <= 991px 5004 991: { 5005 slidesPerView: 1 5006 } 5007 } 5008 }); 5009 }); 5010 } 5011 prevMd = md; 5012 } 5013 } 5014 5015 /** 5016 * Handle hash-based navigation to slides 5017 * Navigates to a slide with matching anchor ID and scrolls to it 5018 */ 5019 var handleHashNavigation = function() { 5020 console.log('handleHashNavigation'); 5021 var hash = window.location.hash; 5022 if (!hash || hash === "") return; 5023 5024 // Find slide with matching ID 5025 var targetSlide = document.querySelector('.promo-carousel__slides-item' + hash); 5026 if (!targetSlide) return; 5027 5028 // Find the parent carousel 5029 var carousel = targetSlide.closest('.promo-carousel'); 5030 if (!carousel) return; 5031 5032 // Get the index of all carousels on the page 5033 var allCarousels = document.querySelectorAll('.promo-carousel'); 5034 var carouselIndex = Array.prototype.indexOf.call(allCarousels, carousel); 5035 5036 // Get the swiper instance for this carousel 5037 var swiperInstance = promoCarouselSwipers[carouselIndex]; 5038 5039 // Get the slide index within this carousel
5040 var slides = carousel.querySelectorAll('.promo-carousel__slides-item'); 5041 var slideIndex = Array.prototype.indexOf.call(slides, targetSlide); 5042 5043 if (slideIndex === -1) return; 5044 5045 // Navigate to slide if swiper exists (mobile) - no animation 5046 if (swiperInstance && swiperInstance.slideTo) { 5047 swiperInstance.slideTo(slideIndex, 0); 5048 } 5049 5050 // Calculate final scroll position as a single value 5051 var scrollAdjustment = parseInt(targetSlide.dataset.scrollAdjustment, 10) || 0; 5052 var carouselOffsetTop = carousel.offsetTop; // Use offsetTop instead of getBoundingClientRect 5053 var finalScrollPosition = carouselOffsetTop + scrollAdjustment; 5054 5055 // Calculate duration based on scroll distance 5056 var currentPosition = window.pageYOffset; 5057 var distance = Math.abs(finalScrollPosition - currentPosition); 5058 var pixelsPerSecond = 1200; 5059 var duration = distance / pixelsPerSecond; 5060 duration = Math.max(0.3, Math.min(1.5, duration)); 5061 5062 setTimeout(function() { 5063 gsap.to(window, { 5064 duration: duration, 5065 scrollTo: { y: finalScrollPosition }, 5066 ease: "power2.out", 5067 onComplete: function() { 5068 if (hash && window.location.hash !== hash) { 5069 window.history.pushState(null, null, hash); 5070 } 5071 } 5072 }); 5073 }, 300); 5074 }; 5075 5076 // Intercept clicks on hash links that match promo carousel slide IDs 5077 $(document).on('click', 'a[href^="#"]', function(e) { 5078 var href = $(this).attr('href'); 5079 if (!href || href === '#') return; 5080 5081 // Only intercept if hash matches a promo carousel slide ID 5082 var targetSlide = document.querySelector('.promo-carousel__slides-item' + href); 5083 if (!targetSlide) return; 5084 5085 e.preventDefault(); 5086 window.history.pushState(null, null, href); 5087 handleHashNavigation(); 5088 }); 5089 5090 // initiate slider 5091 $(window).on('load', function() { 5092 initPromoCarouselSwiper(); 5093 5094 // Check hash after swiper initialization 5095 setTimeout(handleHashNavigation, 500); 5096 }); 5097 5098 $(window).on('resize', function() { 5099 initPromoCarouselSwiper(); 5100 }); 5101 5102 // Listen for hash changes 5103 window.addEventListener('hashchange', handleHashNavigation); 5104})(); 5105 5106/** 5107 * @popperjs/core v2.9.2 - MIT License 5108 */ 5109 5110(function (global, factory) { 5111 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 5112 typeof define === 'function' && define.amd ? define(['exports'], factory) : 5113 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Popper = {})); 5114}(this, (function (exports) { 'use strict'; 5115 5116 function getBoundingClientRect(element) { 5117 var rect = element.getBoundingClientRect(); 5118 return { 5119 width: rect.width, 5120 height: rect.height, 5121 top: rect.top, 5122 right: rect.right, 5123 bottom: rect.bottom, 5124 left: rect.left, 5125 x: rect.left, 5126 y: rect.top 5127 }; 5128 } 5129 5130 function getWindow(node) { 5131 if (node == null) { 5132 return window; 5133 } 5134 5135 if (node.toString() !== '[object Window]') { 5136 var ownerDocument = node.ownerDocument; 5137 return ownerDocument ? ownerDocument.defaultView || window : window; 5138 } 5139 5140 return node; 5141 } 5142 5143 function getWindowScroll(node) { 5144 var win = getWindow(node); 5145 var scrollLeft = win.pageXOffset; 5146 var scrollTop = win.pageYOffset; 5147 return { 5148 scrollLeft: scrollLeft, 5149 scrollTop: scrollTop 5150 }; 5151 } 5152 5153 function isElement(node) { 5154 var OwnElement = getWindow(node).Element; 5155 return node instanceof OwnElement || node instanceof Element; 5156 } 5157 5158 function isHTMLElement(node) { 5159 var OwnElement = getWindow(node).HTMLElement; 5160 return node instanceof OwnElement || node instanceof HTMLElement; 5161 } 5162 5163 function isShadowRoot(node) { 5164 // IE 11 has no ShadowRoot 5165 if (typeof ShadowRoot === 'undefined') { 5166 return false; 5167 } 5168 5169 var OwnElement = getWindow(node).ShadowRoot; 5170 return node instanceof OwnElement || node instanceof ShadowRoot; 5171 } 5172 5173 function getHTMLElementScroll(element) { 5174 return { 5175 scrollLeft: element.scrollLeft, 5176 scrollTop: element.scrollTop 5177 }; 5178 } 5179 5180 function getNodeScroll(node) {
vendor: 5,124 bytes, lines 5181-5336
5181 if (node === getWindow(node) || !isHTMLElement(node)) { 5182 return getWindowScroll(node); 5183 } else { 5184 return getHTMLElementScroll(node); 5185 } 5186 } 5187 5188 function getNodeName(element) { 5189 return element ? (element.nodeName || '').toLowerCase() : null; 5190 } 5191 5192 function getDocumentElement(element) { 5193 // $FlowFixMe[incompatible-return]: assume body is always available 5194 return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing] 5195 element.document) || window.document).documentElement; 5196 } 5197 5198 function getWindowScrollBarX(element) { 5199 // If <html> has a CSS width greater than the viewport, then this will be 5200 // incorrect for RTL. 5201 // Popper 1 is broken in this case and never had a bug report so let's assume 5202 // it's not an issue. I don't think anyone ever specifies width on <html> 5203 // anyway. 5204 // Browsers where the left scrollbar doesn't cause an issue report `0` for 5205 // this (e.g. Edge 2019, IE11, Safari) 5206 return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft; 5207 } 5208 5209 function getComputedStyle(element) { 5210 return getWindow(element).getComputedStyle(element); 5211 } 5212 5213 function isScrollParent(element) { 5214 // Firefox wants us to check `-x` and `-y` variations as well 5215 var _getComputedStyle = getComputedStyle(element), 5216 overflow = _getComputedStyle.overflow, 5217 overflowX = _getComputedStyle.overflowX, 5218 overflowY = _getComputedStyle.overflowY; 5219 5220 return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX); 5221 } 5222 5223 // Composite means it takes into account transforms as well as layout. 5224 5225 function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) { 5226 if (isFixed === void 0) { 5227 isFixed = false; 5228 } 5229 5230 var documentElement = getDocumentElement(offsetParent); 5231 var rect = getBoundingClientRect(elementOrVirtualElement); 5232 var isOffsetParentAnElement = isHTMLElement(offsetParent); 5233 var scroll = { 5234 scrollLeft: 0, 5235 scrollTop: 0 5236 }; 5237 var offsets = { 5238 x: 0, 5239 y: 0 5240 }; 5241 5242 if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { 5243 if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078 5244 isScrollParent(documentElement)) { 5245 scroll = getNodeScroll(offsetParent); 5246 } 5247 5248 if (isHTMLElement(offsetParent)) { 5249 offsets = getBoundingClientRect(offsetParent); 5250 offsets.x += offsetParent.clientLeft; 5251 offsets.y += offsetParent.clientTop; 5252 } else if (documentElement) { 5253 offsets.x = getWindowScrollBarX(documentElement); 5254 } 5255 } 5256 5257 return { 5258 x: rect.left + scroll.scrollLeft - offsets.x, 5259 y: rect.top + scroll.scrollTop - offsets.y, 5260 width: rect.width, 5261 height: rect.height 5262 }; 5263 } 5264 5265 // means it doesn't take into account transforms. 5266 5267 function getLayoutRect(element) { 5268 var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed. 5269 // Fixes https://github.com/popperjs/popper-core/issues/1223 5270 5271 var width = element.offsetWidth; 5272 var height = element.offsetHeight; 5273 5274 if (Math.abs(clientRect.width - width) <= 1) { 5275 width = clientRect.width; 5276 } 5277 5278 if (Math.abs(clientRect.height - height) <= 1) { 5279 height = clientRect.height; 5280 } 5281 5282 return { 5283 x: element.offsetLeft, 5284 y: element.offsetTop, 5285 width: width, 5286 height: height 5287 }; 5288 } 5289 5290 function getParentNode(element) { 5291 if (getNodeName(element) === 'html') { 5292 return element; 5293 } 5294 5295 return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle 5296 // $FlowFixMe[incompatible-return] 5297 // $FlowFixMe[prop-missing] 5298 element.assignedSlot || // step into the shadow DOM of the parent of a slotted node 5299 element.parentNode || ( // DOM Element detected 5300 isShadowRoot(element) ? element.host : null) || // ShadowRoot detected 5301 // $FlowFixMe[incompatible-call]: HTMLElement is a Node 5302 getDocumentElement(element) // fallback 5303 5304 ); 5305 } 5306 5307 function getScrollParent(node) { 5308 if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) { 5309 // $FlowFixMe[incompatible-return]: assume body is always available 5310 return node.ownerDocument.body; 5311 } 5312 5313 if (isHTMLElement(node) && isScrollParent(node)) { 5314 return node; 5315 } 5316 5317 return getScrollParent(getParentNode(node)); 5318 } 5319 5320 /* 5321 given a DOM element, return the list of all scroll parents, up the list of ancesors 5322 until we get to the top window object. This list is what we attach scroll listeners 5323 to, because if any of these parent elements scroll, we'll need to re-calculate the 5324 reference element's position. 5325 */ 5326 5327 function listScrollParents(element, list) { 5328 var _element$ownerDocumen; 5329 5330 if (list === void 0) { 5331 list = []; 5332 } 5333 5334 var scrollParent = getScrollParent(element); 5335 var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body); 5336 var win = getWindow(scrollParent);
vendor: 4,110 bytes, lines 5337-5440
5337 var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent; 5338 var updatedList = list.concat(target); 5339 return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here 5340 updatedList.concat(listScrollParents(getParentNode(target))); 5341 } 5342 5343 function isTableElement(element) { 5344 return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0; 5345 } 5346 5347 function getTrueOffsetParent(element) { 5348 if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837 5349 getComputedStyle(element).position === 'fixed') { 5350 return null; 5351 } 5352 5353 return element.offsetParent; 5354 } // `.offsetParent` reports `null` for fixed elements, while absolute elements 5355 // return the containing block 5356 5357 5358 function getContainingBlock(element) { 5359 var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') !== -1; 5360 var isIE = navigator.userAgent.indexOf('Trident') !== -1; 5361 5362 if (isIE && isHTMLElement(element)) { 5363 // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport 5364 var elementCss = getComputedStyle(element); 5365 5366 if (elementCss.position === 'fixed') { 5367 return null; 5368 } 5369 } 5370 5371 var currentNode = getParentNode(element); 5372 5373 while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) { 5374 var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that 5375 // create a containing block. 5376 // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block 5377 5378 if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') { 5379 return currentNode; 5380 } else { 5381 currentNode = currentNode.parentNode; 5382 } 5383 } 5384 5385 return null; 5386 } // Gets the closest ancestor positioned element. Handles some edge cases, 5387 // such as table ancestors and cross browser bugs. 5388 5389 5390 function getOffsetParent(element) { 5391 var window = getWindow(element); 5392 var offsetParent = getTrueOffsetParent(element); 5393 5394 while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') { 5395 offsetParent = getTrueOffsetParent(offsetParent); 5396 } 5397 5398 if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) { 5399 return window; 5400 } 5401 5402 return offsetParent || getContainingBlock(element) || window; 5403 } 5404 5405 var top = 'top'; 5406 var bottom = 'bottom'; 5407 var right = 'right'; 5408 var left = 'left'; 5409 var auto = 'auto'; 5410 var basePlacements = [top, bottom, right, left]; 5411 var start = 'start'; 5412 var end = 'end'; 5413 var clippingParents = 'clippingParents'; 5414 var viewport = 'viewport'; 5415 var popper = 'popper'; 5416 var reference = 'reference'; 5417 var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) { 5418 return acc.concat([placement + "-" + start, placement + "-" + end]); 5419 }, []); 5420 var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) { 5421 return acc.concat([placement, placement + "-" + start, placement + "-" + end]); 5422 }, []); // modifiers that need to read the DOM 5423 5424 var beforeRead = 'beforeRead'; 5425 var read = 'read'; 5426 var afterRead = 'afterRead'; // pure-logic modifiers 5427 5428 var beforeMain = 'beforeMain'; 5429 var main = 'main'; 5430 var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state) 5431 5432 var beforeWrite = 'beforeWrite'; 5433 var write = 'write'; 5434 var afterWrite = 'afterWrite'; 5435 var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite]; 5436 5437 function order(modifiers) { 5438 var map = new Map(); 5439 var visited = new Set(); 5440 var result = [];
vendor: 6,266 bytes, lines 5441-5626
5441 modifiers.forEach(function (modifier) { 5442 map.set(modifier.name, modifier); 5443 }); // On visiting object, check for its dependencies and visit them recursively 5444 5445 function sort(modifier) { 5446 visited.add(modifier.name); 5447 var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []); 5448 requires.forEach(function (dep) { 5449 if (!visited.has(dep)) { 5450 var depModifier = map.get(dep); 5451 5452 if (depModifier) { 5453 sort(depModifier); 5454 } 5455 } 5456 }); 5457 result.push(modifier); 5458 } 5459 5460 modifiers.forEach(function (modifier) { 5461 if (!visited.has(modifier.name)) { 5462 // check for visited object 5463 sort(modifier); 5464 } 5465 }); 5466 return result; 5467 } 5468 5469 function orderModifiers(modifiers) { 5470 // order based on dependencies 5471 var orderedModifiers = order(modifiers); // order based on phase 5472 5473 return modifierPhases.reduce(function (acc, phase) { 5474 return acc.concat(orderedModifiers.filter(function (modifier) { 5475 return modifier.phase === phase; 5476 })); 5477 }, []); 5478 } 5479 5480 function debounce(fn) { 5481 var pending; 5482 return function () { 5483 if (!pending) { 5484 pending = new Promise(function (resolve) { 5485 Promise.resolve().then(function () { 5486 pending = undefined; 5487 resolve(fn()); 5488 }); 5489 }); 5490 } 5491 5492 return pending; 5493 }; 5494 } 5495 5496 function format(str) { 5497 for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 5498 args[_key - 1] = arguments[_key]; 5499 } 5500 5501 return [].concat(args).reduce(function (p, c) { 5502 return p.replace(/%s/, c); 5503 }, str); 5504 } 5505 5506 var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s'; 5507 var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available'; 5508 var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options']; 5509 function validateModifiers(modifiers) { 5510 modifiers.forEach(function (modifier) { 5511 Object.keys(modifier).forEach(function (key) { 5512 switch (key) { 5513 case 'name': 5514 if (typeof modifier.name !== 'string') { 5515 console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\"")); 5516 } 5517 5518 break; 5519 5520 case 'enabled': 5521 if (typeof modifier.enabled !== 'boolean') { 5522 console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\"")); 5523 } 5524 5525 case 'phase': 5526 if (modifierPhases.indexOf(modifier.phase) < 0) { 5527 console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\"")); 5528 } 5529 5530 break; 5531 5532 case 'fn': 5533 if (typeof modifier.fn !== 'function') { 5534 console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\"")); 5535 } 5536 5537 break; 5538 5539 case 'effect': 5540 if (typeof modifier.effect !== 'function') { 5541 console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\"")); 5542 } 5543 5544 break; 5545 5546 case 'requires': 5547 if (!Array.isArray(modifier.requires)) { 5548 console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\"")); 5549 } 5550 5551 break; 5552 5553 case 'requiresIfExists': 5554 if (!Array.isArray(modifier.requiresIfExists)) { 5555 console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\"")); 5556 } 5557 5558 break; 5559 5560 case 'options': 5561 case 'data': 5562 break; 5563 5564 default: 5565 console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) { 5566 return "\"" + s + "\""; 5567 }).join(', ') + "; but \"" + key + "\" was provided."); 5568 } 5569 5570 modifier.requires && modifier.requires.forEach(function (requirement) { 5571 if (modifiers.find(function (mod) { 5572 return mod.name === requirement; 5573 }) == null) { 5574 console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement)); 5575 } 5576 }); 5577 }); 5578 }); 5579 } 5580 5581 function uniqueBy(arr, fn) { 5582 var identifiers = new Set(); 5583 return arr.filter(function (item) { 5584 var identifier = fn(item); 5585 5586 if (!identifiers.has(identifier)) { 5587 identifiers.add(identifier); 5588 return true; 5589 } 5590 }); 5591 } 5592 5593 function getBasePlacement(placement) { 5594 return placement.split('-')[0]; 5595 } 5596 5597 function mergeByName(modifiers) { 5598 var merged = modifiers.reduce(function (merged, current) { 5599 var existing = merged[current.name]; 5600 merged[current.name] = existing ? Object.assign({}, existing, current, { 5601 options: Object.assign({}, existing.options, current.options), 5602 data: Object.assign({}, existing.data, current.data) 5603 }) : current; 5604 return merged; 5605 }, {}); // IE11 does not support Object.values 5606 5607 return Object.keys(merged).map(function (key) { 5608 return merged[key]; 5609 }); 5610 } 5611 5612 function getViewportRect(element) { 5613 var win = getWindow(element); 5614 var html = getDocumentElement(element); 5615 var visualViewport = win.visualViewport; 5616 var width = html.clientWidth; 5617 var height = html.clientHeight; 5618 var x = 0; 5619 var y = 0; // NB: This isn't supported on iOS <= 12. If the keyboard is open, the popper 5620 // can be obscured underneath it. 5621 // Also, `html.clientHeight` adds the bottom bar height in Safari iOS, even 5622 // if it isn't open, so if this isn't available, the popper will be detected 5623 // to overflow the bottom of the screen too early. 5624 5625 if (visualViewport) { 5626 width = visualViewport.width;
vendor: 4,327 bytes, lines 5627-5747
5627 height = visualViewport.height; // Uses Layout Viewport (like Chrome; Safari does not currently) 5628 // In Chrome, it returns a value very close to 0 (+/-) but contains rounding 5629 // errors due to floating point numbers, so we need to check precision. 5630 // Safari returns a number <= 0, usually < -1 when pinch-zoomed 5631 // Feature detection fails in mobile emulation mode in Chrome. 5632 // Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) < 5633 // 0.001 5634 // Fallback here: "Not Safari" userAgent 5635 5636 if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { 5637 x = visualViewport.offsetLeft; 5638 y = visualViewport.offsetTop; 5639 } 5640 } 5641 5642 return { 5643 width: width, 5644 height: height, 5645 x: x + getWindowScrollBarX(element), 5646 y: y 5647 }; 5648 } 5649 5650 var max = Math.max; 5651 var min = Math.min; 5652 var round = Math.round; 5653 5654 // of the `<html>` and `<body>` rect bounds if horizontally scrollable 5655 5656 function getDocumentRect(element) { 5657 var _element$ownerDocumen; 5658 5659 var html = getDocumentElement(element); 5660 var winScroll = getWindowScroll(element); 5661 var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body; 5662 var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0); 5663 var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0); 5664 var x = -winScroll.scrollLeft + getWindowScrollBarX(element); 5665 var y = -winScroll.scrollTop; 5666 5667 if (getComputedStyle(body || html).direction === 'rtl') { 5668 x += max(html.clientWidth, body ? body.clientWidth : 0) - width; 5669 } 5670 5671 return { 5672 width: width, 5673 height: height, 5674 x: x, 5675 y: y 5676 }; 5677 } 5678 5679 function contains(parent, child) { 5680 var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method 5681 5682 if (parent.contains(child)) { 5683 return true; 5684 } // then fallback to custom implementation with Shadow DOM support 5685 else if (rootNode && isShadowRoot(rootNode)) { 5686 var next = child; 5687 5688 do { 5689 if (next && parent.isSameNode(next)) { 5690 return true; 5691 } // $FlowFixMe[prop-missing]: need a better way to handle this... 5692 5693 5694 next = next.parentNode || next.host; 5695 } while (next); 5696 } // Give up, the result is false 5697 5698 5699 return false; 5700 } 5701 5702 function rectToClientRect(rect) { 5703 return Object.assign({}, rect, { 5704 left: rect.x, 5705 top: rect.y, 5706 right: rect.x + rect.width, 5707 bottom: rect.y + rect.height 5708 }); 5709 } 5710 5711 function getInnerBoundingClientRect(element) { 5712 var rect = getBoundingClientRect(element); 5713 rect.top = rect.top + element.clientTop; 5714 rect.left = rect.left + element.clientLeft; 5715 rect.bottom = rect.top + element.clientHeight; 5716 rect.right = rect.left + element.clientWidth; 5717 rect.width = element.clientWidth; 5718 rect.height = element.clientHeight; 5719 rect.x = rect.left; 5720 rect.y = rect.top; 5721 return rect; 5722 } 5723 5724 function getClientRectFromMixedType(element, clippingParent) { 5725 return clippingParent === viewport ? rectToClientRect(getViewportRect(element)) : isHTMLElement(clippingParent) ? getInnerBoundingClientRect(clippingParent) : rectToClientRect(getDocumentRect(getDocumentElement(element))); 5726 } // A "clipping parent" is an overflowable container with the characteristic of 5727 // clipping (or hiding) overflowing elements with a position different from 5728 // `initial` 5729 5730 5731 function getClippingParents(element) { 5732 var clippingParents = listScrollParents(getParentNode(element)); 5733 var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0; 5734 var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element; 5735 5736 if (!isElement(clipperElement)) { 5737 return []; 5738 } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414 5739 5740 5741 return clippingParents.filter(function (clippingParent) { 5742 return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body'; 5743 }); 5744 } // Gets the maximum area that the element is visible in due to any number of 5745 // clipping parents 5746 5747
vendor: 4,117 bytes, lines 5748-5877
5748 function getClippingRect(element, boundary, rootBoundary) { 5749 var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary); 5750 var clippingParents = [].concat(mainClippingParents, [rootBoundary]); 5751 var firstClippingParent = clippingParents[0]; 5752 var clippingRect = clippingParents.reduce(function (accRect, clippingParent) { 5753 var rect = getClientRectFromMixedType(element, clippingParent); 5754 accRect.top = max(rect.top, accRect.top); 5755 accRect.right = min(rect.right, accRect.right); 5756 accRect.bottom = min(rect.bottom, accRect.bottom); 5757 accRect.left = max(rect.left, accRect.left); 5758 return accRect; 5759 }, getClientRectFromMixedType(element, firstClippingParent)); 5760 clippingRect.width = clippingRect.right - clippingRect.left; 5761 clippingRect.height = clippingRect.bottom - clippingRect.top; 5762 clippingRect.x = clippingRect.left; 5763 clippingRect.y = clippingRect.top; 5764 return clippingRect; 5765 } 5766 5767 function getVariation(placement) { 5768 return placement.split('-')[1]; 5769 } 5770 5771 function getMainAxisFromPlacement(placement) { 5772 return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y'; 5773 } 5774 5775 function computeOffsets(_ref) { 5776 var reference = _ref.reference, 5777 element = _ref.element, 5778 placement = _ref.placement; 5779 var basePlacement = placement ? getBasePlacement(placement) : null; 5780 var variation = placement ? getVariation(placement) : null; 5781 var commonX = reference.x + reference.width / 2 - element.width / 2; 5782 var commonY = reference.y + reference.height / 2 - element.height / 2; 5783 var offsets; 5784 5785 switch (basePlacement) { 5786 case top: 5787 offsets = { 5788 x: commonX, 5789 y: reference.y - element.height 5790 }; 5791 break; 5792 5793 case bottom: 5794 offsets = { 5795 x: commonX, 5796 y: reference.y + reference.height 5797 }; 5798 break; 5799 5800 case right: 5801 offsets = { 5802 x: reference.x + reference.width, 5803 y: commonY 5804 }; 5805 break; 5806 5807 case left: 5808 offsets = { 5809 x: reference.x - element.width, 5810 y: commonY 5811 }; 5812 break; 5813 5814 default: 5815 offsets = { 5816 x: reference.x, 5817 y: reference.y 5818 }; 5819 } 5820 5821 var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null; 5822 5823 if (mainAxis != null) { 5824 var len = mainAxis === 'y' ? 'height' : 'width'; 5825 5826 switch (variation) { 5827 case start: 5828 offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2); 5829 break; 5830 5831 case end: 5832 offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2); 5833 break; 5834 } 5835 } 5836 5837 return offsets; 5838 } 5839 5840 function getFreshSideObject() { 5841 return { 5842 top: 0, 5843 right: 0, 5844 bottom: 0, 5845 left: 0 5846 }; 5847 } 5848 5849 function mergePaddingObject(paddingObject) { 5850 return Object.assign({}, getFreshSideObject(), paddingObject); 5851 } 5852 5853 function expandToHashMap(value, keys) { 5854 return keys.reduce(function (hashMap, key) { 5855 hashMap[key] = value; 5856 return hashMap; 5857 }, {}); 5858 } 5859 5860 function detectOverflow(state, options) { 5861 if (options === void 0) { 5862 options = {}; 5863 } 5864 5865 var _options = options, 5866 _options$placement = _options.placement, 5867 placement = _options$placement === void 0 ? state.placement : _options$placement, 5868 _options$boundary = _options.boundary, 5869 boundary = _options$boundary === void 0 ? clippingParents : _options$boundary, 5870 _options$rootBoundary = _options.rootBoundary, 5871 rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary, 5872 _options$elementConte = _options.elementContext, 5873 elementContext = _options$elementConte === void 0 ? popper : _options$elementConte, 5874 _options$altBoundary = _options.altBoundary, 5875 altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary, 5876 _options$padding = _options.padding, 5877 padding = _options$padding === void 0 ? 0 : _options$padding;
vendor: 4,535 bytes, lines 5878-5977
5878 var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); 5879 var altContext = elementContext === popper ? reference : popper; 5880 var referenceElement = state.elements.reference; 5881 var popperRect = state.rects.popper; 5882 var element = state.elements[altBoundary ? altContext : elementContext]; 5883 var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary); 5884 var referenceClientRect = getBoundingClientRect(referenceElement); 5885 var popperOffsets = computeOffsets({ 5886 reference: referenceClientRect, 5887 element: popperRect, 5888 strategy: 'absolute', 5889 placement: placement 5890 }); 5891 var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets)); 5892 var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect 5893 // 0 or negative = within the clipping rect 5894 5895 var overflowOffsets = { 5896 top: clippingClientRect.top - elementClientRect.top + paddingObject.top, 5897 bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom, 5898 left: clippingClientRect.left - elementClientRect.left + paddingObject.left, 5899 right: elementClientRect.right - clippingClientRect.right + paddingObject.right 5900 }; 5901 var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element 5902 5903 if (elementContext === popper && offsetData) { 5904 var offset = offsetData[placement]; 5905 Object.keys(overflowOffsets).forEach(function (key) { 5906 var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1; 5907 var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x'; 5908 overflowOffsets[key] += offset[axis] * multiply; 5909 }); 5910 } 5911 5912 return overflowOffsets; 5913 } 5914 5915 var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.'; 5916 var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.'; 5917 var DEFAULT_OPTIONS = { 5918 placement: 'bottom', 5919 modifiers: [], 5920 strategy: 'absolute' 5921 }; 5922 5923 function areValidElements() { 5924 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 5925 args[_key] = arguments[_key]; 5926 } 5927 5928 return !args.some(function (element) { 5929 return !(element && typeof element.getBoundingClientRect === 'function'); 5930 }); 5931 } 5932 5933 function popperGenerator(generatorOptions) { 5934 if (generatorOptions === void 0) { 5935 generatorOptions = {}; 5936 } 5937 5938 var _generatorOptions = generatorOptions, 5939 _generatorOptions$def = _generatorOptions.defaultModifiers, 5940 defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def, 5941 _generatorOptions$def2 = _generatorOptions.defaultOptions, 5942 defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2; 5943 return function createPopper(reference, popper, options) { 5944 if (options === void 0) { 5945 options = defaultOptions; 5946 } 5947 5948 var state = { 5949 placement: 'bottom', 5950 orderedModifiers: [], 5951 options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions), 5952 modifiersData: {}, 5953 elements: { 5954 reference: reference, 5955 popper: popper 5956 }, 5957 attributes: {}, 5958 styles: {} 5959 }; 5960 var effectCleanupFns = []; 5961 var isDestroyed = false; 5962 var instance = { 5963 state: state, 5964 setOptions: function setOptions(options) { 5965 cleanupModifierEffects(); 5966 state.options = Object.assign({}, defaultOptions, state.options, options); 5967 state.scrollParents = { 5968 reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [], 5969 popper: listScrollParents(popper) 5970 }; // Orders the modifiers based on their dependencies and `phase` 5971 // properties 5972 5973 var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers 5974 5975 state.orderedModifiers = orderedModifiers.filter(function (m) { 5976 return m.enabled; 5977 }); // Validate the provided modifiers so that the consumer will get warned
vendor: 4,110 bytes, lines 5978-6071
5978 // if one of the modifiers is invalid for any reason 5979 5980 { 5981 var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) { 5982 var name = _ref.name; 5983 return name; 5984 }); 5985 validateModifiers(modifiers); 5986 5987 if (getBasePlacement(state.options.placement) === auto) { 5988 var flipModifier = state.orderedModifiers.find(function (_ref2) { 5989 var name = _ref2.name; 5990 return name === 'flip'; 5991 }); 5992 5993 if (!flipModifier) { 5994 console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' ')); 5995 } 5996 } 5997 5998 var _getComputedStyle = getComputedStyle(popper), 5999 marginTop = _getComputedStyle.marginTop, 6000 marginRight = _getComputedStyle.marginRight, 6001 marginBottom = _getComputedStyle.marginBottom, 6002 marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can 6003 // cause bugs with positioning, so we'll warn the consumer 6004 6005 6006 if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) { 6007 return parseFloat(margin); 6008 })) { 6009 console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' ')); 6010 } 6011 } 6012 6013 runModifierEffects(); 6014 return instance.update(); 6015 }, 6016 // Sync update â it will always be executed, even if not necessary. This 6017 // is useful for low frequency updates where sync behavior simplifies the 6018 // logic. 6019 // For high frequency updates (e.g. `resize` and `scroll` events), always 6020 // prefer the async Popper#update method 6021 forceUpdate: function forceUpdate() { 6022 if (isDestroyed) { 6023 return; 6024 } 6025 6026 var _state$elements = state.elements, 6027 reference = _state$elements.reference, 6028 popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements 6029 // anymore 6030 6031 if (!areValidElements(reference, popper)) { 6032 { 6033 console.error(INVALID_ELEMENT_ERROR); 6034 } 6035 6036 return; 6037 } // Store the reference and popper rects to be read by modifiers 6038 6039 6040 state.rects = { 6041 reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'), 6042 popper: getLayoutRect(popper) 6043 }; // Modifiers have the ability to reset the current update cycle. The 6044 // most common use case for this is the `flip` modifier changing the 6045 // placement, which then needs to re-run all the modifiers, because the 6046 // logic was previously ran for the previous placement and is therefore 6047 // stale/incorrect 6048 6049 state.reset = false; 6050 state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier 6051 // is filled with the initial data specified by the modifier. This means 6052 // it doesn't persist and is fresh on each update. 6053 // To ensure persistent data, use `${name}#persistent` 6054 6055 state.orderedModifiers.forEach(function (modifier) { 6056 return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); 6057 }); 6058 var __debug_loops__ = 0; 6059 6060 for (var index = 0; index < state.orderedModifiers.length; index++) { 6061 { 6062 __debug_loops__ += 1; 6063 6064 if (__debug_loops__ > 100) { 6065 console.error(INFINITE_LOOP_ERROR); 6066 break; 6067 } 6068 } 6069 6070 if (state.reset === true) { 6071 state.reset = false;
vendor: 6,095 bytes, lines 6072-6275
6072 index = -1; 6073 continue; 6074 } 6075 6076 var _state$orderedModifie = state.orderedModifiers[index], 6077 fn = _state$orderedModifie.fn, 6078 _state$orderedModifie2 = _state$orderedModifie.options, 6079 _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2, 6080 name = _state$orderedModifie.name; 6081 6082 if (typeof fn === 'function') { 6083 state = fn({ 6084 state: state, 6085 options: _options, 6086 name: name, 6087 instance: instance 6088 }) || state; 6089 } 6090 } 6091 }, 6092 // Async and optimistically optimized update â it will not be executed if 6093 // not necessary (debounced to run at most once-per-tick) 6094 update: debounce(function () { 6095 return new Promise(function (resolve) { 6096 instance.forceUpdate(); 6097 resolve(state); 6098 }); 6099 }), 6100 destroy: function destroy() { 6101 cleanupModifierEffects(); 6102 isDestroyed = true; 6103 } 6104 }; 6105 6106 if (!areValidElements(reference, popper)) { 6107 { 6108 console.error(INVALID_ELEMENT_ERROR); 6109 } 6110 6111 return instance; 6112 } 6113 6114 instance.setOptions(options).then(function (state) { 6115 if (!isDestroyed && options.onFirstUpdate) { 6116 options.onFirstUpdate(state); 6117 } 6118 }); // Modifiers have the ability to execute arbitrary code before the first 6119 // update cycle runs. They will be executed in the same order as the update 6120 // cycle. This is useful when a modifier adds some persistent data that 6121 // other modifiers need to use, but the modifier is run after the dependent 6122 // one. 6123 6124 function runModifierEffects() { 6125 state.orderedModifiers.forEach(function (_ref3) { 6126 var name = _ref3.name, 6127 _ref3$options = _ref3.options, 6128 options = _ref3$options === void 0 ? {} : _ref3$options, 6129 effect = _ref3.effect; 6130 6131 if (typeof effect === 'function') { 6132 var cleanupFn = effect({ 6133 state: state, 6134 name: name, 6135 instance: instance, 6136 options: options 6137 }); 6138 6139 var noopFn = function noopFn() {}; 6140 6141 effectCleanupFns.push(cleanupFn || noopFn); 6142 } 6143 }); 6144 } 6145 6146 function cleanupModifierEffects() { 6147 effectCleanupFns.forEach(function (fn) { 6148 return fn(); 6149 }); 6150 effectCleanupFns = []; 6151 } 6152 6153 return instance; 6154 }; 6155 } 6156 6157 var passive = { 6158 passive: true 6159 }; 6160 6161 function effect$2(_ref) { 6162 var state = _ref.state, 6163 instance = _ref.instance, 6164 options = _ref.options; 6165 var _options$scroll = options.scroll, 6166 scroll = _options$scroll === void 0 ? true : _options$scroll, 6167 _options$resize = options.resize, 6168 resize = _options$resize === void 0 ? true : _options$resize; 6169 var window = getWindow(state.elements.popper); 6170 var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper); 6171 6172 if (scroll) { 6173 scrollParents.forEach(function (scrollParent) { 6174 scrollParent.addEventListener('scroll', instance.update, passive); 6175 }); 6176 } 6177 6178 if (resize) { 6179 window.addEventListener('resize', instance.update, passive); 6180 } 6181 6182 return function () { 6183 if (scroll) { 6184 scrollParents.forEach(function (scrollParent) { 6185 scrollParent.removeEventListener('scroll', instance.update, passive); 6186 }); 6187 } 6188 6189 if (resize) { 6190 window.removeEventListener('resize', instance.update, passive); 6191 } 6192 }; 6193 } // eslint-disable-next-line import/no-unused-modules 6194 6195 6196 var eventListeners = { 6197 name: 'eventListeners', 6198 enabled: true, 6199 phase: 'write', 6200 fn: function fn() {}, 6201 effect: effect$2, 6202 data: {} 6203 }; 6204 6205 function popperOffsets(_ref) { 6206 var state = _ref.state, 6207 name = _ref.name; 6208 // Offsets are the actual position the popper needs to have to be 6209 // properly positioned near its reference element 6210 // This is the most basic placement, and will be adjusted by 6211 // the modifiers in the next step 6212 state.modifiersData[name] = computeOffsets({ 6213 reference: state.rects.reference, 6214 element: state.rects.popper, 6215 strategy: 'absolute', 6216 placement: state.placement 6217 }); 6218 } // eslint-disable-next-line import/no-unused-modules 6219 6220 6221 var popperOffsets$1 = { 6222 name: 'popperOffsets', 6223 enabled: true, 6224 phase: 'read', 6225 fn: popperOffsets, 6226 data: {} 6227 }; 6228 6229 var unsetSides = { 6230 top: 'auto', 6231 right: 'auto', 6232 bottom: 'auto', 6233 left: 'auto' 6234 }; // Round the offsets to the nearest suitable subpixel based on the DPR. 6235 // Zooming can change the DPR, but it seems to report a value that will 6236 // cleanly divide the values into the appropriate subpixels. 6237 6238 function roundOffsetsByDPR(_ref) { 6239 var x = _ref.x, 6240 y = _ref.y; 6241 var win = window; 6242 var dpr = win.devicePixelRatio || 1; 6243 return { 6244 x: round(round(x * dpr) / dpr) || 0, 6245 y: round(round(y * dpr) / dpr) || 0 6246 }; 6247 } 6248 6249 function mapToStyles(_ref2) { 6250 var _Object$assign2; 6251 6252 var popper = _ref2.popper, 6253 popperRect = _ref2.popperRect, 6254 placement = _ref2.placement, 6255 offsets = _ref2.offsets, 6256 position = _ref2.position, 6257 gpuAcceleration = _ref2.gpuAcceleration, 6258 adaptive = _ref2.adaptive, 6259 roundOffsets = _ref2.roundOffsets; 6260 6261 var _ref3 = roundOffsets === true ? roundOffsetsByDPR(offsets) : typeof roundOffsets === 'function' ? roundOffsets(offsets) : offsets, 6262 _ref3$x = _ref3.x, 6263 x = _ref3$x === void 0 ? 0 : _ref3$x, 6264 _ref3$y = _ref3.y, 6265 y = _ref3$y === void 0 ? 0 : _ref3$y; 6266 6267 var hasX = offsets.hasOwnProperty('x'); 6268 var hasY = offsets.hasOwnProperty('y'); 6269 var sideX = left; 6270 var sideY = top; 6271 var win = window; 6272 6273 if (adaptive) { 6274 var offsetParent = getOffsetParent(popper); 6275 var heightProp = 'clientHeight';
6276 var widthProp = 'clientWidth'; 6277 6278 if (offsetParent === getWindow(popper)) { 6279 offsetParent = getDocumentElement(popper); 6280 6281 if (getComputedStyle(offsetParent).position !== 'static') { 6282 heightProp = 'scrollHeight'; 6283 widthProp = 'scrollWidth'; 6284 } 6285 } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it 6286 6287 6288 offsetParent = offsetParent; 6289 6290 if (placement === top) { 6291 sideY = bottom; // $FlowFixMe[prop-missing] 6292 6293 y -= offsetParent[heightProp] - popperRect.height; 6294 y *= gpuAcceleration ? 1 : -1; 6295 } 6296 6297 if (placement === left) { 6298 sideX = right; // $FlowFixMe[prop-missing] 6299 6300 x -= offsetParent[widthProp] - popperRect.width; 6301 x *= gpuAcceleration ? 1 : -1; 6302 } 6303 } 6304 6305 var commonStyles = Object.assign({ 6306 position: position 6307 }, adaptive && unsetSides); 6308 6309 if (gpuAcceleration) { 6310 var _Object$assign; 6311 6312 return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) < 2 ? "translate(" + x + "px, " + y + "px)" : "translate3d(" + x + "px, " + y + "px, 0)", _Object$assign)); 6313 } 6314 6315 return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + "px" : '', _Object$assign2[sideX] = hasX ? x + "px" : '', _Object$assign2.transform = '', _Object$assign2)); 6316 } 6317 6318 function computeStyles(_ref4) { 6319 var state = _ref4.state, 6320 options = _ref4.options; 6321 var _options$gpuAccelerat = options.gpuAcceleration, 6322 gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat, 6323 _options$adaptive = options.adaptive, 6324 adaptive = _options$adaptive === void 0 ? true : _options$adaptive, 6325 _options$roundOffsets = options.roundOffsets, 6326 roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; 6327 6328 { 6329 var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || ''; 6330 6331 if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) { 6332 return transitionProperty.indexOf(property) >= 0; 6333 })) { 6334 console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' ')); 6335 } 6336 } 6337 6338 var commonStyles = { 6339 placement: getBasePlacement(state.placement), 6340 popper: state.elements.popper, 6341 popperRect: state.rects.popper, 6342 gpuAcceleration: gpuAcceleration 6343 }; 6344 6345 if (state.modifiersData.popperOffsets != null) { 6346 state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, { 6347 offsets: state.modifiersData.popperOffsets, 6348 position: state.options.strategy, 6349 adaptive: adaptive, 6350 roundOffsets: roundOffsets 6351 }))); 6352 } 6353 6354 if (state.modifiersData.arrow != null) { 6355 state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, { 6356 offsets: state.modifiersData.arrow, 6357 position: 'absolute', 6358 adaptive: false, 6359 roundOffsets: roundOffsets 6360 }))); 6361 } 6362 6363 state.attributes.popper = Object.assign({}, state.attributes.popper, { 6364 'data-popper-placement': state.placement 6365 }); 6366 } // eslint-disable-next-line import/no-unused-modules 6367 6368 6369 var computeStyles$1 = { 6370 name: 'computeStyles', 6371 enabled: true, 6372 phase: 'beforeWrite', 6373 fn: computeStyles, 6374 data: {} 6375 }; 6376 6377 // and applies them to the HTMLElements such as popper and arrow 6378 6379 function applyStyles(_ref) { 6380 var state = _ref.state;
vendor: 8,677 bytes, lines 6381-6635
6381 Object.keys(state.elements).forEach(function (name) { 6382 var style = state.styles[name] || {}; 6383 var attributes = state.attributes[name] || {}; 6384 var element = state.elements[name]; // arrow is optional + virtual elements 6385 6386 if (!isHTMLElement(element) || !getNodeName(element)) { 6387 return; 6388 } // Flow doesn't support to extend this property, but it's the most 6389 // effective way to apply styles to an HTMLElement 6390 // $FlowFixMe[cannot-write] 6391 6392 6393 Object.assign(element.style, style); 6394 Object.keys(attributes).forEach(function (name) { 6395 var value = attributes[name]; 6396 6397 if (value === false) { 6398 element.removeAttribute(name); 6399 } else { 6400 element.setAttribute(name, value === true ? '' : value); 6401 } 6402 }); 6403 }); 6404 } 6405 6406 function effect$1(_ref2) { 6407 var state = _ref2.state; 6408 var initialStyles = { 6409 popper: { 6410 position: state.options.strategy, 6411 left: '0', 6412 top: '0', 6413 margin: '0' 6414 }, 6415 arrow: { 6416 position: 'absolute' 6417 }, 6418 reference: {} 6419 }; 6420 Object.assign(state.elements.popper.style, initialStyles.popper); 6421 state.styles = initialStyles; 6422 6423 if (state.elements.arrow) { 6424 Object.assign(state.elements.arrow.style, initialStyles.arrow); 6425 } 6426 6427 return function () { 6428 Object.keys(state.elements).forEach(function (name) { 6429 var element = state.elements[name]; 6430 var attributes = state.attributes[name] || {}; 6431 var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them 6432 6433 var style = styleProperties.reduce(function (style, property) { 6434 style[property] = ''; 6435 return style; 6436 }, {}); // arrow is optional + virtual elements 6437 6438 if (!isHTMLElement(element) || !getNodeName(element)) { 6439 return; 6440 } 6441 6442 Object.assign(element.style, style); 6443 Object.keys(attributes).forEach(function (attribute) { 6444 element.removeAttribute(attribute); 6445 }); 6446 }); 6447 }; 6448 } // eslint-disable-next-line import/no-unused-modules 6449 6450 6451 var applyStyles$1 = { 6452 name: 'applyStyles', 6453 enabled: true, 6454 phase: 'write', 6455 fn: applyStyles, 6456 effect: effect$1, 6457 requires: ['computeStyles'] 6458 }; 6459 6460 function distanceAndSkiddingToXY(placement, rects, offset) { 6461 var basePlacement = getBasePlacement(placement); 6462 var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1; 6463 6464 var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, { 6465 placement: placement 6466 })) : offset, 6467 skidding = _ref[0], 6468 distance = _ref[1]; 6469 6470 skidding = skidding || 0; 6471 distance = (distance || 0) * invertDistance; 6472 return [left, right].indexOf(basePlacement) >= 0 ? { 6473 x: distance, 6474 y: skidding 6475 } : { 6476 x: skidding, 6477 y: distance 6478 }; 6479 } 6480 6481 function offset(_ref2) { 6482 var state = _ref2.state, 6483 options = _ref2.options, 6484 name = _ref2.name; 6485 var _options$offset = options.offset, 6486 offset = _options$offset === void 0 ? [0, 0] : _options$offset; 6487 var data = placements.reduce(function (acc, placement) { 6488 acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset); 6489 return acc; 6490 }, {}); 6491 var _data$state$placement = data[state.placement], 6492 x = _data$state$placement.x, 6493 y = _data$state$placement.y; 6494 6495 if (state.modifiersData.popperOffsets != null) { 6496 state.modifiersData.popperOffsets.x += x; 6497 state.modifiersData.popperOffsets.y += y; 6498 } 6499 6500 state.modifiersData[name] = data; 6501 } // eslint-disable-next-line import/no-unused-modules 6502 6503 6504 var offset$1 = { 6505 name: 'offset', 6506 enabled: true, 6507 phase: 'main', 6508 requires: ['popperOffsets'], 6509 fn: offset 6510 }; 6511 6512 var hash$1 = { 6513 left: 'right', 6514 right: 'left', 6515 bottom: 'top', 6516 top: 'bottom' 6517 }; 6518 function getOppositePlacement(placement) { 6519 return placement.replace(/left|right|bottom|top/g, function (matched) { 6520 return hash$1[matched]; 6521 }); 6522 } 6523 6524 var hash = { 6525 start: 'end', 6526 end: 'start' 6527 }; 6528 function getOppositeVariationPlacement(placement) { 6529 return placement.replace(/start|end/g, function (matched) { 6530 return hash[matched]; 6531 }); 6532 } 6533 6534 function computeAutoPlacement(state, options) { 6535 if (options === void 0) { 6536 options = {}; 6537 } 6538 6539 var _options = options, 6540 placement = _options.placement, 6541 boundary = _options.boundary, 6542 rootBoundary = _options.rootBoundary, 6543 padding = _options.padding, 6544 flipVariations = _options.flipVariations, 6545 _options$allowedAutoP = _options.allowedAutoPlacements, 6546 allowedAutoPlacements = _options$allowedAutoP === void 0 ? placements : _options$allowedAutoP; 6547 var variation = getVariation(placement); 6548 var placements$1 = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) { 6549 return getVariation(placement) === variation; 6550 }) : basePlacements; 6551 var allowedPlacements = placements$1.filter(function (placement) { 6552 return allowedAutoPlacements.indexOf(placement) >= 0; 6553 }); 6554 6555 if (allowedPlacements.length === 0) { 6556 allowedPlacements = placements$1; 6557 6558 { 6559 console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, "auto" cannot be used to allow "bottom-start".', 'Use "auto-start" instead.'].join(' ')); 6560 } 6561 } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... 6562 6563 6564 var overflows = allowedPlacements.reduce(function (acc, placement) { 6565 acc[placement] = detectOverflow(state, { 6566 placement: placement, 6567 boundary: boundary, 6568 rootBoundary: rootBoundary, 6569 padding: padding 6570 })[getBasePlacement(placement)]; 6571 return acc; 6572 }, {}); 6573 return Object.keys(overflows).sort(function (a, b) { 6574 return overflows[a] - overflows[b]; 6575 }); 6576 } 6577 6578 function getExpandedFallbackPlacements(placement) { 6579 if (getBasePlacement(placement) === auto) { 6580 return []; 6581 } 6582 6583 var oppositePlacement = getOppositePlacement(placement); 6584 return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)]; 6585 } 6586 6587 function flip(_ref) { 6588 var state = _ref.state, 6589 options = _ref.options, 6590 name = _ref.name; 6591 6592 if (state.modifiersData[name]._skip) { 6593 return; 6594 } 6595 6596 var _options$mainAxis = options.mainAxis, 6597 checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, 6598 _options$altAxis = options.altAxis, 6599 checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis, 6600 specifiedFallbackPlacements = options.fallbackPlacements, 6601 padding = options.padding, 6602 boundary = options.boundary, 6603 rootBoundary = options.rootBoundary, 6604 altBoundary = options.altBoundary, 6605 _options$flipVariatio = options.flipVariations, 6606 flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio, 6607 allowedAutoPlacements = options.allowedAutoPlacements; 6608 var preferredPlacement = state.options.placement; 6609 var basePlacement = getBasePlacement(preferredPlacement); 6610 var isBasePlacement = basePlacement === preferredPlacement; 6611 var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement)); 6612 var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) { 6613 return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, { 6614 placement: placement, 6615 boundary: boundary, 6616 rootBoundary: rootBoundary, 6617 padding: padding, 6618 flipVariations: flipVariations, 6619 allowedAutoPlacements: allowedAutoPlacements 6620 }) : placement); 6621 }, []); 6622 var referenceRect = state.rects.reference; 6623 var popperRect = state.rects.popper; 6624 var checksMap = new Map(); 6625 var makeFallbackChecks = true; 6626 var firstFittingPlacement = placements[0]; 6627 6628 for (var i = 0; i < placements.length; i++) { 6629 var placement = placements[i]; 6630 6631 var _basePlacement = getBasePlacement(placement); 6632 6633 var isStartVariation = getVariation(placement) === start; 6634 var isVertical = [top, bottom].indexOf(_basePlacement) >= 0; 6635 var len = isVertical ? 'width' : 'height';
vendor: 9,084 bytes, lines 6636-6874
6636 var overflow = detectOverflow(state, { 6637 placement: placement, 6638 boundary: boundary, 6639 rootBoundary: rootBoundary, 6640 altBoundary: altBoundary, 6641 padding: padding 6642 }); 6643 var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top; 6644 6645 if (referenceRect[len] > popperRect[len]) { 6646 mainVariationSide = getOppositePlacement(mainVariationSide); 6647 } 6648 6649 var altVariationSide = getOppositePlacement(mainVariationSide); 6650 var checks = []; 6651 6652 if (checkMainAxis) { 6653 checks.push(overflow[_basePlacement] <= 0); 6654 } 6655 6656 if (checkAltAxis) { 6657 checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0); 6658 } 6659 6660 if (checks.every(function (check) { 6661 return check; 6662 })) { 6663 firstFittingPlacement = placement; 6664 makeFallbackChecks = false; 6665 break; 6666 } 6667 6668 checksMap.set(placement, checks); 6669 } 6670 6671 if (makeFallbackChecks) { 6672 // `2` may be desired in some cases â research later 6673 var numberOfChecks = flipVariations ? 3 : 1; 6674 6675 var _loop = function _loop(_i) { 6676 var fittingPlacement = placements.find(function (placement) { 6677 var checks = checksMap.get(placement); 6678 6679 if (checks) { 6680 return checks.slice(0, _i).every(function (check) { 6681 return check; 6682 }); 6683 } 6684 }); 6685 6686 if (fittingPlacement) { 6687 firstFittingPlacement = fittingPlacement; 6688 return "break"; 6689 } 6690 }; 6691 6692 for (var _i = numberOfChecks; _i > 0; _i--) { 6693 var _ret = _loop(_i); 6694 6695 if (_ret === "break") break; 6696 } 6697 } 6698 6699 if (state.placement !== firstFittingPlacement) { 6700 state.modifiersData[name]._skip = true; 6701 state.placement = firstFittingPlacement; 6702 state.reset = true; 6703 } 6704 } // eslint-disable-next-line import/no-unused-modules 6705 6706 6707 var flip$1 = { 6708 name: 'flip', 6709 enabled: true, 6710 phase: 'main', 6711 fn: flip, 6712 requiresIfExists: ['offset'], 6713 data: { 6714 _skip: false 6715 } 6716 }; 6717 6718 function getAltAxis(axis) { 6719 return axis === 'x' ? 'y' : 'x'; 6720 } 6721 6722 function within(min$1, value, max$1) { 6723 return max(min$1, min(value, max$1)); 6724 } 6725 6726 function preventOverflow(_ref) { 6727 var state = _ref.state, 6728 options = _ref.options, 6729 name = _ref.name; 6730 var _options$mainAxis = options.mainAxis, 6731 checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, 6732 _options$altAxis = options.altAxis, 6733 checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis, 6734 boundary = options.boundary, 6735 rootBoundary = options.rootBoundary, 6736 altBoundary = options.altBoundary, 6737 padding = options.padding, 6738 _options$tether = options.tether, 6739 tether = _options$tether === void 0 ? true : _options$tether, 6740 _options$tetherOffset = options.tetherOffset, 6741 tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset; 6742 var overflow = detectOverflow(state, { 6743 boundary: boundary, 6744 rootBoundary: rootBoundary, 6745 padding: padding, 6746 altBoundary: altBoundary 6747 }); 6748 var basePlacement = getBasePlacement(state.placement); 6749 var variation = getVariation(state.placement); 6750 var isBasePlacement = !variation; 6751 var mainAxis = getMainAxisFromPlacement(basePlacement); 6752 var altAxis = getAltAxis(mainAxis); 6753 var popperOffsets = state.modifiersData.popperOffsets; 6754 var referenceRect = state.rects.reference; 6755 var popperRect = state.rects.popper; 6756 var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, { 6757 placement: state.placement 6758 })) : tetherOffset; 6759 var data = { 6760 x: 0, 6761 y: 0 6762 }; 6763 6764 if (!popperOffsets) { 6765 return; 6766 } 6767 6768 if (checkMainAxis || checkAltAxis) { 6769 var mainSide = mainAxis === 'y' ? top : left; 6770 var altSide = mainAxis === 'y' ? bottom : right; 6771 var len = mainAxis === 'y' ? 'height' : 'width'; 6772 var offset = popperOffsets[mainAxis]; 6773 var min$1 = popperOffsets[mainAxis] + overflow[mainSide]; 6774 var max$1 = popperOffsets[mainAxis] - overflow[altSide]; 6775 var additive = tether ? -popperRect[len] / 2 : 0; 6776 var minLen = variation === start ? referenceRect[len] : popperRect[len]; 6777 var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go 6778 // outside the reference bounds 6779 6780 var arrowElement = state.elements.arrow; 6781 var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : { 6782 width: 0, 6783 height: 0 6784 }; 6785 var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject(); 6786 var arrowPaddingMin = arrowPaddingObject[mainSide]; 6787 var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want 6788 // to include its full size in the calculation. If the reference is small 6789 // and near the edge of a boundary, the popper can overflow even if the 6790 // reference is not overflowing as well (e.g. virtual elements with no 6791 // width or height) 6792 6793 var arrowLen = within(0, referenceRect[len], arrowRect[len]); 6794 var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - tetherOffsetValue : minLen - arrowLen - arrowPaddingMin - tetherOffsetValue; 6795 var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + tetherOffsetValue : maxLen + arrowLen + arrowPaddingMax + tetherOffsetValue; 6796 var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow); 6797 var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0; 6798 var offsetModifierValue = state.modifiersData.offset ? state.modifiersData.offset[state.placement][mainAxis] : 0; 6799 var tetherMin = popperOffsets[mainAxis] + minOffset - offsetModifierValue - clientOffset; 6800 var tetherMax = popperOffsets[mainAxis] + maxOffset - offsetModifierValue; 6801 6802 if (checkMainAxis) { 6803 var preventedOffset = within(tether ? min(min$1, tetherMin) : min$1, offset, tether ? max(max$1, tetherMax) : max$1); 6804 popperOffsets[mainAxis] = preventedOffset; 6805 data[mainAxis] = preventedOffset - offset; 6806 } 6807 6808 if (checkAltAxis) { 6809 var _mainSide = mainAxis === 'x' ? top : left; 6810 6811 var _altSide = mainAxis === 'x' ? bottom : right; 6812 6813 var _offset = popperOffsets[altAxis]; 6814 6815 var _min = _offset + overflow[_mainSide]; 6816 6817 var _max = _offset - overflow[_altSide]; 6818 6819 var _preventedOffset = within(tether ? min(_min, tetherMin) : _min, _offset, tether ? max(_max, tetherMax) : _max); 6820 6821 popperOffsets[altAxis] = _preventedOffset; 6822 data[altAxis] = _preventedOffset - _offset; 6823 } 6824 } 6825 6826 state.modifiersData[name] = data; 6827 } // eslint-disable-next-line import/no-unused-modules 6828 6829 6830 var preventOverflow$1 = { 6831 name: 'preventOverflow', 6832 enabled: true, 6833 phase: 'main', 6834 fn: preventOverflow, 6835 requiresIfExists: ['offset'] 6836 }; 6837 6838 var toPaddingObject = function toPaddingObject(padding, state) { 6839 padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, { 6840 placement: state.placement 6841 })) : padding; 6842 return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); 6843 }; 6844 6845 function arrow(_ref) { 6846 var _state$modifiersData$; 6847 6848 var state = _ref.state, 6849 name = _ref.name, 6850 options = _ref.options; 6851 var arrowElement = state.elements.arrow; 6852 var popperOffsets = state.modifiersData.popperOffsets; 6853 var basePlacement = getBasePlacement(state.placement); 6854 var axis = getMainAxisFromPlacement(basePlacement); 6855 var isVertical = [left, right].indexOf(basePlacement) >= 0; 6856 var len = isVertical ? 'height' : 'width'; 6857 6858 if (!arrowElement || !popperOffsets) { 6859 return; 6860 } 6861 6862 var paddingObject = toPaddingObject(options.padding, state); 6863 var arrowRect = getLayoutRect(arrowElement); 6864 var minProp = axis === 'y' ? top : left; 6865 var maxProp = axis === 'y' ? bottom : right; 6866 var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len]; 6867 var startDiff = popperOffsets[axis] - state.rects.reference[axis]; 6868 var arrowOffsetParent = getOffsetParent(arrowElement); 6869 var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0; 6870 var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is 6871 // outside of the popper bounds 6872 6873 var min = paddingObject[minProp]; 6874 var max = clientSize - arrowRect[len] - paddingObject[maxPro
6874p]; 6875 var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference; 6876 var offset = within(min, center, max); // Prevents breaking syntax highlighting... 6877 6878 var axisProp = axis; 6879 state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$); 6880 } 6881 6882 function effect(_ref2) { 6883 var state = _ref2.state, 6884 options = _ref2.options; 6885 var _options$element = options.element, 6886 arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element; 6887 6888 if (arrowElement == null) { 6889 return; 6890 } // CSS selector 6891 6892 6893 if (typeof arrowElement === 'string') { 6894 arrowElement = state.elements.popper.querySelector(arrowElement); 6895 6896 if (!arrowElement) { 6897 return; 6898 } 6899 } 6900 6901 { 6902 if (!isHTMLElement(arrowElement)) { 6903 console.error(['Popper: "arrow" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' ')); 6904 } 6905 } 6906 6907 if (!contains(state.elements.popper, arrowElement)) { 6908 { 6909 console.error(['Popper: "arrow" modifier\'s `element` must be a child of the popper', 'element.'].join(' ')); 6910 } 6911 6912 return; 6913 } 6914 6915 state.elements.arrow = arrowElement; 6916 } // eslint-disable-next-line import/no-unused-modules 6917 6918 6919 var arrow$1 = { 6920 name: 'arrow', 6921 enabled: true, 6922 phase: 'main', 6923 fn: arrow, 6924 effect: effect, 6925 requires: ['popperOffsets'], 6926 requiresIfExists: ['preventOverflow'] 6927 }; 6928 6929 function getSideOffsets(overflow, rect, preventedOffsets) { 6930 if (preventedOffsets === void 0) { 6931 preventedOffsets = { 6932 x: 0, 6933 y: 0 6934 }; 6935 } 6936 6937 return { 6938 top: overflow.top - rect.height - preventedOffsets.y, 6939 right: overflow.right - rect.width + preventedOffsets.x, 6940 bottom: overflow.bottom - rect.height + preventedOffsets.y, 6941 left: overflow.left - rect.width - preventedOffsets.x 6942 }; 6943 } 6944 6945 function isAnySideFullyClipped(overflow) { 6946 return [top, right, bottom, left].some(function (side) { 6947 return overflow[side] >= 0; 6948 }); 6949 } 6950 6951 function hide(_ref) { 6952 var state = _ref.state, 6953 name = _ref.name; 6954 var referenceRect = state.rects.reference; 6955 var popperRect = state.rects.popper; 6956 var preventedOffsets = state.modifiersData.preventOverflow; 6957 var referenceOverflow = detectOverflow(state, { 6958 elementContext: 'reference' 6959 }); 6960 var popperAltOverflow = detectOverflow(state, { 6961 altBoundary: true 6962 }); 6963 var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect); 6964 var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets); 6965 var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets); 6966 var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets); 6967 state.modifiersData[name] = { 6968 referenceClippingOffsets: referenceClippingOffsets, 6969 popperEscapeOffsets: popperEscapeOffsets, 6970 isReferenceHidden: isReferenceHidden, 6971 hasPopperEscaped: hasPopperEscaped 6972 }; 6973 state.attributes.popper = Object.assign({}, state.attributes.popper, { 6974 'data-popper-reference-hidden': isReferenceHidden, 6975 'data-popper-escaped': hasPopperEscaped 6976 }); 6977 } // eslint-disable-next-line import/no-unused-modules 6978 6979 6980 var hide$1 = { 6981 name: 'hide', 6982 enabled: true, 6983 phase: 'main', 6984 requiresIfExists: ['preventOverflow'], 6985 fn: hide 6986 }; 6987 6988 var defaultModifiers$1 = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1]; 6989 var createPopper$1 = /*#__PURE__*/popperGenerator({ 6990 defaultModifiers: defaultModifiers$1 6991 }); // eslint-disable-next-line import/no-unused-modules 6992 6993 var defaultModifiers = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1, offset$1, flip$1, preventOverflow$1, arrow$1, hide$1]; 6994 var createPopper = /*#__PURE__*/popperGenerator({ 6995 defaultModifiers: defaultModifiers 6996 }); // eslint-disable-next-line import/no-unused-modules 6997 6998 exports.applyStyles = applyStyles$1; 6999 exports.arrow = arrow$1; 7000 exports.computeStyles = computeStyles$1; 7001 exports.createPopper = createPopper; 7002 exports.createPopperLite = createPopper$1; 7003 exports.defaultModifiers = defaultModifiers; 7004 exports.detectOverflow = detectOverflow; 7005 exports.eventListeners = eventListeners; 7006 exports.flip = flip$1; 7007 exports.hide = hide$1; 7008 exports.offset = offset$1; 7009 exports.popperGenerator = popperGenerator; 7010 exports.popperOffsets = popperOffsets$1; 7011 exports.preventOverflow = preventOverflow$1; 7012 7013 Object.defineProperty(exports, '__esModule', { value: true }); 7014 7015}))); 7016$(function(){ 7017 7018 function setupPopper(targetID, popoverID) 7019 { 7020 var popcorn = document.querySelector('#'+targetID); 7021 var tooltip = document.querySelector('#'+popoverID); 7022 var width = document.getElementById(targetID).getBoundingClientRect().width*0.5 - 8 + (137 - document.getElementById(targetID).getBoundingClientRect().width);//document.getElementById(targetID).offsetWidth * -1; 7023 7024 if (window.innerWidth >= 992) { 7025 Popper.createPopper(popcorn, tooltip, { 7026 placement: 'bottom', 7027 modifiers: [ 7028 { 7029 name: 'offset', 7030 options: { 7031 offset: [width, 15], 7032 }, 7033 }, 7034 ], 7035 }); 7036 } 7037 else { 7038 Popper.createPopper(popcorn, tooltip, { 7039 placement: 'top', 7040 modifiers: [ 7041 { 7042 name: 'offset', 7043 options: { 7044 offset: [0, 15], 7045 }, 7046 }, 7047 ], 7048 }); 7049 7050 } 7051 7052 } 7053 7054 function popoverCookieExists(popoverID) 7055 { 7056 var headline = $('#'+popoverID).data('headline'); 7057 var modifiedDate = $('#'+popoverID).data('modified-date'); 7058 var cookieString = headline + '-' +modifiedDate; 7059 return document.cookie.indexOf(cookieString) > -1; 7060 } 7061 7062 function createPopoverCookie(popoverID) 7063 { 7064 var headline = $('#'+popoverID).data('headline'); 7065 var modifiedDate = $('#'+popoverID).data('modified-date'); 7066 var cookieString = headline + '-' +modifiedDate; 7067 //1 year 7068 var expirationDate = new Date(); 7069 expirationDate = new Date(expirationDate.getTime() +1000*60*60*24*365); 7070 document.cookie = 'popover='+cookieString+'; expires='+expirationDate.toGMTString()+';'; 7071 } 7072 7073 function setupPopoverInteraction(targetID, popoverID) 7074 { 7075 //Desktop - Hover 7076 $('#'+targetID).hover(function(){ 7077 if (window.innerWidth >= 992) { 7078 setupPopper(targetID, popoverID) 7079 $('#'+popoverID).removeClass('popover-alert--hide'); 7080 } 7081 },function(){ 7082 if (window.innerWidth >= 992) { 7083 //Check if use is hovering over popover 7084 7085 if(window.document.documentMode) 7086 { 7087 setTimeout(function(){ 7088 var popoverHover = $('#'+popoverID).is(":hover") 7089 7090 if (!popoverHover) { 7091 $('#'+popoverID).addClass('popover-alert--hide'); 7092 } 7093 }, 250); 7094 } 7095 else { 7096 var popoverHover = $('#'+popoverID).is(":hover") 7097 7098 if (!popoverHover) { 7099 $('#'+popoverID).addClass('popover-alert--hide'); 7100 } 7101 } 7102 } 7103 }); 7104 7105 //Desktop - Hide popover when leaving it 7106 $('#'+popoverID).on('mouseleave', function(){ 7107 if ($('#'+targetID + ':hover').length == 0) { 7108 $('#'+popoverID).addClass('popover-alert--hide'); 7109 } 7110 }); 7111 7112 //Mobile 7113 $('#'+targetID).click(function(e){ 7114 if (window.innerWidth < 992) { 7115 if($('#'+popoverID).hasClass('popover-alert--hide') && !popoverCookieExists(popoverID)) 7116 { 7117 e.preventDefault(); 7118 $('#'+popoverID).removeClass('popover-alert--hide'); 7119 setupPopper(targetID, popoverID) 7120 } 7121 } 7122 }); 7123 7124 //Reset popover on resize 7125 $(window).on('resize', function() { 7126 setupPopper(targetID, popoverID); 7127 $('#'+popoverID).addClass('popover-alert--hide'); 7128 }); 7129 7130 $('.popover-alert__cta').click(function(){ 7131 $('#'+popoverID).addClass('popover-alert--hide'); 7132 createPopoverCookie(popoverID); 7133 }); 7134 7135 $('.header__menu-trigger ').click(function(){ 7136 $('#'+popoverID).addClass('popover-alert--hide'); 7137 }); 7138 } 7139 $(document).ready(function(){ 7140 $('.popover-alert').each(function(){ 7141 7142 //Setup 7143 var targetID = $(this).data('target'); 7144 var popoverID = $(this).attr('id'); 7145 7146 setupPopper(targetID, popoverID); 7147 $('#'+popoverID).removeClass('popover-alert--invisible'); 7148 $('#'+popoverID).addClass('popover-alert--hide'); 7149 if(!popoverCookieExists(popoverID)) 7150 { 7151 setupPopoverInteraction(targetID, popoverID); 7152 } 7153 7154 }) 7155 }) 7156}); 7157var pipelineMapList = []; 7158var jsonData = []; 7159 7160addEventListener('DOMContentLoaded', () => { 7161 //initial setup 7162 $('.pipeline-map').each(function (index, map) { 7163 var hybridTilt = $(map).data('hybrid-tilt'); 7164 var satelliteTilt = $(map).data('satellite-tilt'); 7165 var type = $(map).data('map-type'); 7166 var setTilt = 0; 7167 var id = $(map).attr('id'); 7168 console.log($(map)) 7169 console.log(id); 7170 if (type == 'satellite' && satelliteTilt) { 7171 setTilt = 45; 7172 } 7173 if (type == 'hybrid' && hybridTilt) { 7174 setTilt = 45; 7175 } 7176 //Create a Google map under pipe map 7177 let newMap = new google.maps.Map(map, { 7178 zoom: $(map).data('zoom'), 7179 center: { 7180 lat: parseFloat($(map).data('latitude')), 7181 lng: parseFloat($(map).data('longitude')), 7182 }, 7183 mapTypeId: $(map).data('map-type'), 7184 mapId: id, 7185 tilt: setTilt, 7186 }); 7187 pipelineMapList.push(newMap); 7188 createData(index); 7189 }); 7190 function createData(index) { 7191 $($('.google-map-wrapper')[index]) 7192 .find('.pipeline-map--data') 7193 .each(function () { 7194 //Get Data point Data 7195 var name = $(this).data('name'); 7196 var color = $(this).data('color'); 7197 var snapToRoad = $(this).data('snap-to-road') != null; 7198 var thickness = $(this).data('thickness'); 7199 var opacity = $(this).data('opacity'); 7200 7201 var data = []; 7202 $(this) 7203 .children('.pipeline-map--coordinate') 7204 .each(function () { 7205 data.push({ 7206 lng: parseFloat($(this).data('longitude')), 7207 lat: parseFloat($(this).data('latitude')), 7208 }); 7209 }); 7210 7211 if (!snapToRoad) { 7212 createDefaultLine(data, color, thickness, opacity, index); 7213 } 7214 if (snapToRoad) { 7215 createSnapToRoadLine(data, color, thickness, opacity, index); 7216 } 7217 createPipelineMarkers(index); 7218 }); 7219 } 7220}); 7221 7222function createDefaultLine(data, color, thickness, opacity, index) { 7223 var planCoordinates = data; 7224 var path = new google.maps.Polyline({ 7225 path: planCoordinates, 7226 geodesic: true, 7227 strokeColor: color, 7228 strokeOpacity: opacity, 7229 strokeWeight: thickness, 7230 }); 7231 path.setMap(pipelineMapList[index]); 7232} 7233 7234function createSnapToRoadLine(data, color, thickness, opacity, index) { 7235 // Create a URL for the Snap to Roads Google API request. 7236 var url = 7237 'https://roads.googleapis.com/v1/snapToRoads?path=' + 7238 data 7239 .map(function (point) { 7240 return point.lat + ',' + point.lng; 7241 }) 7242 .join('|'); 7243 url += '&interpolate=true'; 7244 (url += '&key=' + $($('.pipeline-map')[index]).data('api')), 7245 // Make the request to the Google API. 7246 $.ajax({ 7247 url: url, 7248 dataType: 'json', 7249 success: function (response) { 7250 // Get the snapped points from the response. 7251 var snappedPoints = response.snappedPoints; 7252 var output = []; 7253 for (var i = 0; i < snappedPoints.length; i++) { 7254 // Get the location object from the current `snappedPoints` object. 7255 var location = snappedPoints[i]['location']; 7256 7257 // Add the latitude and longitude to the output array. 7258 output.push({ 7259 lat: location['latitude'], 7260 lng: location['longitude'], 7261 }); 7262 } 7263 // Create a polyline object from the snapped points. 7264 var polyline = new google.maps.Polyline({ 7265 path: output, 7266 strokeColor: color, 7267 strokeOpacity: opacity, 7268 strokeWeight: thickness, 7269 }); 7270 7271 // Add the polyline object to the map. 7272 polyline.setMap(pipelineMapList[index]); 7273 }, 7274 }); 7275} 7276 7277function createPipelineMarkers(index) { 7278 var markers = []; 7279 $($('.pipelinemap .google-map-wrapper')[index]) 7280 .find('.pipeline-map--marker') 7281 .each(function () { 7282 var pointTitle = $(this).data('title'); 7283 var pointIcon = $(this).data('custom-icon'); 7284 var pointContent = $(this).data('content'); 7285 // Create the content for the info window 7286 7287 var infowindowContent = ""; 7288 infowindowContent = pointTitle != undefined ? infowindowContent + "<h5 class='marker--title mb-3'>" + 7289 pointTitle + 7290 '</h5>' : infowindowContent + ""; 7291 7292 infowindowContent = pointContent != undefined ? infowindowContent + "<p class='marker-content'>" + 7293 pointContent + 7294 '</p>' : infowindowContent + ""; 7295 var infowindow = new google.maps.InfoWindow({ 7296 content: infowindowContent, 7297 }); 7298 7299 7300 var markerElement = null; 7301 // Create the element for the AdvancedMarkerElement 7302 if (pointIcon != null && pointIcon != undefined) { 7303 markerElement = document.createElement('div'); 7304 markerElement.classList.add('custom-marker'); 7305 markerElement.innerHTML = 7306 "<img src='" + pointIcon + "' alt='" + pointTitle + "' />"; 7307 } 7308 7309 var marker = new google.maps.marker.AdvancedMarkerElement({ 7310 position: { 7311 lat: parseFloat($(this).data('latitude')), 7312 lng: parseFloat($(this).data('longitude')), 7313 }, 7314 title: pointTitle, 7315 content: markerElement, // Set the custom marker content 7316 map: pipelineMapList[index], // Add to the map 7317 }); 7318 7319 // Add a click listener for each marker to open the info window 7320 marker.addListener('click', function () { 7321 infowindow.open({ 7322 map: pipelineMapList[index], 7323 anchor: marker, 7324 shouldFocus: false, 7325 }); 7326 }); 7327 7328 markers.push(marker); 7329 }); 7330} 7331 7332/** 7333 * Page Animations - Scroll Effects 7334 * 7335 * This script handles: 7336 * - Hiding scrollbars on specified elements 7337 * - Applying GSAP ScrollTrigger parallax effects to elements 7338 * 7339 * Uses ScrollTrigger with scrub for parallax effects (no wrapper structure needed). 7340 * Speed: < 1 = slower/parallax up, > 1 = faster, 1 = normal 7341 * Lag: Smoothing delay in seconds (maps to scrub value) 7342 */ 7343 7344document.addEventListener('DOMContentLoaded', function() { 7345 const pageAnimationsElement = document.querySelector('.pageanimations[data-instance-id]'); 7346 7347 if (!pageAnimationsElement) { 7348 return; 7349 } 7350 7351 // Parse hide scrollbar selectors 7352 let hideScrollbarSelectors = []; 7353 try { 7354 hideScrollbarSelectors = JSON.parse(pageAnimationsElement.dataset.hideScrollbarSelectors || '[]'); 7355 } catch (e) { 7356 console.warn('Page Animations: Failed to parse hideScrollbarSelectors JSON'); 7357 } 7358 7359 // Parse scroll effects 7360 let scrollEffects = []; 7361 try { 7362 scrollEffects = JSON.parse(pageAnimationsElement.dataset.scrollEffects || '[]'); 7363 } catch (e) { 7364 console.warn('Page Animations: Failed to parse scrollEffects JS
7364ON'); 7365 } 7366 7367 // Handle hide scrollbar for each specified element 7368 if (hideScrollbarSelectors && hideScrollbarSelectors.length > 0) { 7369 hideScrollbarSelectors.forEach(function(selector) { 7370 const elements = document.querySelectorAll(selector); 7371 elements.forEach(function(el) { 7372 el.classList.add('hide-page-scrollbar'); 7373 }); 7374 }); 7375 } 7376 7377 // Initialize ScrollTrigger parallax effects if there are any configured 7378 if (scrollEffects && scrollEffects.length > 0) { 7379 initScrollTriggerEffects(scrollEffects); 7380 } 7381}); 7382 7383/** 7384 * Initialize GSAP ScrollTrigger parallax effects 7385 * @param {Array} effects - Array of effect configurations 7386 */ 7387const initScrollTriggerEffects = function(effects) { 7388 // Check for GSAP availability 7389 if (typeof gsap === 'undefined') { 7390 console.warn('Page Animations: GSAP is not available. Scroll effects will not be applied.'); 7391 return; 7392 } 7393 7394 // Check for ScrollTrigger 7395 if (typeof ScrollTrigger === 'undefined') { 7396 console.warn('Page Animations: ScrollTrigger plugin is not available. Scroll effects will not be applied.'); 7397 return; 7398 } 7399 7400 // Register plugin 7401 gsap.registerPlugin(ScrollTrigger); 7402 7403 // Apply effects to each configured element 7404 effects.forEach(function(effect) { 7405 if (!effect.elementSelector) { 7406 return; 7407 } 7408 7409 const elements = document.querySelectorAll(effect.elementSelector); 7410 if (elements.length === 0) { 7411 return; 7412 } 7413 7414 // Convert speed to yPercent 7415 // speed < 1 = element moves slower (parallax up effect) 7416 // speed > 1 = element moves faster 7417 // speed = 1 = no effect (normal scroll) 7418 const speed = effect.speed !== undefined ? effect.speed : 1; 7419 7420 // Skip if speed is 1 (no parallax effect) 7421 if (speed === 1) { 7422 return; 7423 } 7424 7425 // Calculate movement: (1 - speed) * 100 gives us the relative movement 7426 // speed 0.5 = yPercent -50 (moves up 50% relative to scroll) 7427 // speed 1.5 = yPercent 50 (moves down 50% relative to scroll) 7428 const yPercent = (1 - speed) * 100; 7429 7430 // Lag becomes the scrub value (smoothing delay) 7431 const scrub = effect.lag !== undefined && effect.lag > 0 ? effect.lag : true; 7432 7433 // Get custom scroll container if specified 7434 const scrollContainer = effect.scrollContainer || null; 7435 7436 elements.forEach(function(element) { 7437 const scrollTriggerConfig = { 7438 trigger: element, 7439 start: 'top bottom', 7440 end: 'bottom top', 7441 scrub: scrub 7442 }; 7443 7444 // Add custom scroller if specified 7445 if (scrollContainer) { 7446 scrollTriggerConfig.scroller = scrollContainer; 7447 } 7448 7449 gsap.to(element, { 7450 yPercent: yPercent, 7451 ease: 'none', 7452 scrollTrigger: scrollTriggerConfig 7453 }); 7454 }); 7455 }); 7456}; 7457 7458/** 7459 * Page Animations - GSAP Directionally Aware Header 7460 * 7461 * This script handles the header hide/show behavior using GSAP ScrollTrigger. 7462 * The header hides when scrolling down and shows when scrolling up. 7463 * 7464 * Configuration: 7465 * - Header Selector: CSS selector for header element (auto-detects if empty) 7466 * - Scroll Container Selector: CSS selector for scroll container (uses html if empty) 7467 */ 7468 7469document.addEventListener('DOMContentLoaded', function() { 7470 const pageAnimationsElement = document.querySelector('.pageanimations[data-enable-hide-on-scroll="true"]'); 7471 7472 if (!pageAnimationsElement) { 7473 return; 7474 } 7475 7476 const enableHideOnScroll = pageAnimationsElement.dataset.enableHideOnScroll === 'true'; 7477 7478 if (!enableHideOnScroll) { 7479 return; 7480 } 7481 7482 // Check if GSAP and ScrollTrigger are available 7483 if (typeof gsap === 'undefined') { 7484 console.warn('Page Animations: GSAP is not available'); 7485 return; 7486 } 7487 7488 if (typeof ScrollTrigger === 'undefined') { 7489 console.warn('Page Animations: GSAP ScrollTrigger plugin is not available'); 7490 return; 7491 } 7492 7493 // Register ScrollTrigger plugin 7494 gsap.registerPlugin(ScrollTrigger); 7495 7496 /** 7497 * Gets the header element using the provided selector or auto-detection 7498 * @param {string} headerSelector - CSS selector for header element 7499 * @returns {HTMLElement|null} Header element or null if not found 7500 */ 7501 const getHeader = (headerSelector) => { 7502 if (headerSelector && headerSelector.trim()) { 7503 return document.querySelector(headerSelector); 7504 } 7505 // Auto-detection fallback 7506 return document.querySelector('header, .header, #header, [role="banner"]'); 7507 }; 7508 7509 /** 7510 * Gets the scroll container element using the provided selector or default to html 7511 * @param {string}
7511 scrollContainerSelector - CSS selector for scroll container 7512 * @returns {HTMLElement} Scroll container element (document.documentElement if not specified) 7513 */ 7514 const getScrollContainer = (scrollContainerSelector) => { 7515 if (scrollContainerSelector && scrollContainerSelector.trim()) { 7516 const container = document.querySelector(scrollContainerSelector); 7517 if (container) { 7518 return container; 7519 } 7520 console.warn('Page Animations: Scroll container not found, using default html'); 7521 } 7522 // Return html element as default 7523 return document.documentElement; 7524 }; 7525 7526 // Get configuration from data attributes 7527 const headerSelector = pageAnimationsElement.dataset.headerSelector || ''; 7528 const scrollContainerSelector = pageAnimationsElement.dataset.scrollContainerSelector || ''; 7529 7530 // Get elements 7531 const header = getHeader(headerSelector); 7532 const scrollContainer = getScrollContainer(scrollContainerSelector); 7533 7534 if (!header) { 7535 console.warn('Page Animations: No header element found'); 7536 return; 7537 } 7538 7539 var headerHeight = header.offsetHeight; 7540 var isHeaderHidden = false; 7541 var refreshSuppressed = false; 7542 7543 const suppressRefresh = () => { 7544 if (refreshSuppressed) return; 7545 refreshSuppressed = true; 7546 ScrollTrigger.config({ autoRefreshEvents: 'none' }); 7547 }; 7548 7549 const restoreRefresh = () => { 7550 if (!refreshSuppressed) return; 7551 refreshSuppressed = false; 7552 ScrollTrigger.config({ autoRefreshEvents: 'visibilitychange,DOMContentLoaded,load' }); 7553 }; 7554 7555 const showAnim = gsap.timeline({ paused: true }) 7556 .from(header, { 7557 yPercent: -100, 7558 duration: 0.2, 7559 force3D: true, 7560 onStart: suppressRefresh, 7561 onComplete: restoreRefresh, 7562 onReverseComplete: restoreRefresh 7563 }, 0); 7564 7565 showAnim.progress(1); 7566 7567 const marginTarget = '.experiencefragment:first-of-type + .responsivegrid'; 7568 var siteHeaderHeight = $('.site-header').height(); 7569 7570 const buildMarginAnim = (targetHeight) => { 7571 var anim = gsap.timeline({ paused: true }) 7572 .fromTo(marginTarget, { 7573 marginTop: 0 7574 }, { 7575 marginTop: targetHeight, 7576 duration: 0.2, 7577 onStart: suppressRefresh, 7578 onComplete: restoreRefresh, 7579 onReverseComplete: restoreRefresh 7580 }, 0); 7581 anim.progress(1); 7582 return anim; 7583 }; 7584 7585 var marginAnim = buildMarginAnim(siteHeaderHeight); 7586 7587 ScrollTrigger.create({
7588 scroller: scrollContainer, 7589 start: "top top", 7590 end: "max", 7591 onUpdate: (self) => { 7592 const scrollPosition = scrollContainer === document.documentElement 7593 ? window.scrollY 7594 : scrollContainer.scrollTop; 7595 7596 if (!isHeaderHidden && self.direction === 1 && scrollPosition > headerHeight) { 7597 isHeaderHidden = true; 7598 showAnim.reverse(); 7599 marginAnim.reverse(); 7600 } else if (isHeaderHidden && self.direction === -1) { 7601 isHeaderHidden = false; 7602 showAnim.play(); 7603 marginAnim.play(); 7604 } 7605 } 7606 }); 7607 7608 var resizeTimeout; 7609 window.addEventListener('resize', function() { 7610 clearTimeout(resizeTimeout); 7611 resizeTimeout = setTimeout(function() { 7612 headerHeight = header.offsetHeight; 7613 var newSiteHeaderHeight = $('.site-header').height(); 7614 if (newSiteHeaderHeight !== siteHeaderHeight) { 7615 siteHeaderHeight = newSiteHeaderHeight; 7616 marginAnim.kill(); 7617 marginAnim = buildMarginAnim(siteHeaderHeight); 7618 } 7619 isHeaderHidden = false; 7620 showAnim.progress(1); 7621 ScrollTrigger.refresh(); 7622 }, 250); 7623 }); 7624}); 7625 7626/** 7627 * Page Animations - Scroll Lock 7628 * 7629 * Prevents internal scrolling on specified containers until the page 7630 * is scrolled to the very top (window.scrollY === 0). Once the page 7631 * reaches the top, the container's internal scroll is unlocked. 7632 * 7633 * Configuration is read from the data-scroll-lock-selectors attribute 7634 * on the .pageanimations element (JSON array of CSS selectors). 7635 */ 7636 7637document.addEventListener('DOMContentLoaded', function() { 7638 const pageAnimationsElement = document.querySelector('.pageanimations[data-instance-id]'); 7639 7640 if (!pageAnimationsElement) { 7641 return; 7642 } 7643 7644 // Parse scroll lock selectors from data attribute 7645 let scrollLockSelectors = []; 7646 try { 7647 scrollLockSelectors = JSON.parse(pageAnimationsElement.dataset.scrollLockSelectors || '[]'); 7648 } catch (e) { 7649 console.warn('Page Animations: Failed to parse scrollLockSelectors JSON'); 7650 } 7651 7652 if (!scrollLockSelectors || scrollLockSelectors.length === 0) { 7653 return; 7654 } 7655 7656 /** 7657 * Tracks each locked container with its element reference and original 7658 * overflow style so we can restore it cleanly when the page hits the top. 7659 * @type {Array<{element: HTMLElement, originalOverflow: string}>} 7660 */ 7661 const lockedContainers = []; 7662 7663 // Resolve selectors to elements and store their original overflow values 7664 scrollLockSelectors.forEach(function(selector) { 7665 const elements = document.querySelectorAll(selector); 7666 elements.forEach(function(el) { 7667 lockedContainers.push({ 7668 element: el, 7669 originalOverflow: el.style.overflow || '' 7670 }); 7671 }); 7672 }); 7673 7674 if (lockedContainers.length === 0) { 7675 return; 7676 } 7677 7678 /** 7679 * Applies or removes the scroll lock on every tracked container 7680 * based on the current page scroll position. 7681 */ 7682 const handleScrollLock = function() { 7683 const isAtTop = window.scrollY === 0; 7684 7685 lockedContainers.forEach(function(container) { 7686 if (isAtTop) { 7687 // Unlock: restore the original overflow value 7688 container.element.style.overflow = container.originalOverflow; 7689 } else { 7690 // Lock: prevent internal scrolling 7691 container.element.style.overflow = 'hidden'; 7692 } 7693 }); 7694 }; 7695 7696 // Set initial state 7697 handleScrollLock(); 7698 7699 // Listen for scroll with passive flag for performance 7700 window.addEventListener('scroll', handleScrollLock, { passive: true }); 7701}); 7702 7703/*! 7704 * jQuery.ellipsis 7705 * http://github.com/jjenzz/jquery.ellipsis 7706 * -------------------------------------------------------------------------- 7707 * Copyright (c) 2013 J. Smith (@jjenzz) 7708 * Dual licensed under the MIT and GPL licenses: 7709 * http://www.opensource.org/licenses/mit-license.php 7710 * http://www.gnu.org/licenses/gpl.html 7711 * 7712 * adds a class to the last 'allowed' line of text so you can apply 7713 * text-overflow: ellipsis; 7714 */ 7715(function(factory) { 7716 'use strict'; 7717 7718 if (typeof define === 'function' && define.amd) { 7719 // AMD. register as an anonymous module 7720 define(['jquery'], factory); 7721 } else { 7722 // browser globals 7723 factory(jQuery); 7724 } 7725}(function($) { 7726 'use strict'; 7727 7728 var namespace = 'ellipsis', 7729 span = '<span style="white-space: nowrap;">', 7730 defaults = { 7731 lines: 'auto', 7732 ellipClass: 'ellip', 7733 responsive: false 7734 }; 7735 7736 /** 7737 * Ellipsis() 7738 * -------------------------------------------------------------------------- 7739 * @param {Node} el 7740 * @param {Object} opts 7741 */ 7742 function Ellipsis(el, opts) { 7743 var base = this, 7744 currLine = 0, 7745 lines = [], 7746 setStartEllipAt, 7747 startEllipAt, 7748 resizeTimer, 7749 currOffset, 7750 lineHeight, 7751 contHeight, 7752 words, 7753 htmlEntities; 7754 7755 // List of HTML entities for escaping. 7756 htmlEntities = { 7757 '&': '&', 7758 '<': '<', 7759 '>': '>', 7760 '"': '"', 7761 "'": ''', 7762 '`': '`', 7763 '/': '/', 7764 '\\': '\' 7765 }; 7766 7767 base.$cont = $(el); 7768 base.opts = $.extend({}, defaults, opts); 7769 7770 /** 7771 * create() happens once when 7772 * instance is created 7773 */ 7774 function create() { 7775 base.text = base.$cont.text(); 7776 base.opts.ellipLineClass = base.opts.ellipClass + '-line'; 7777 7778 base.$el = $('<span class="' + base.opts.ellipClass + '" />'); 7779 base.$el.text(base.text); 7780 7781 base.$cont.empty().append(base.$el); 7782 7783 init(); 7784 } 7785 7786 /**
7787 * init() 7788 */ 7789 function init() { 7790 7791 // if they only want one line just add 7792 // the class and do nothing else 7793 if (typeof base.opts.lines === 'number' && base.opts.lines < 2) { 7794 base.$el.addClass(base.opts.ellipLineClass); 7795 return; 7796 } 7797 7798 contHeight = base.$cont.height(); 7799 7800 // if they only want to ellipsis the overflow 7801 // then do nothing if there is no overflow 7802 if (base.opts.lines === 'auto' && base.$el.prop('scrollHeight') <= contHeight) { 7803 return; 7804 } 7805 7806 if (!setStartEllipAt) { 7807 return; 7808 } 7809 7810 // create an array of words from our string 7811 words = $.trim(escapeText(base.text)).split(/\s+/); 7812 7813 // wrap each word in a span and temporarily append to the DOM 7814 base.$el.html(span + words.join('</span> ' + span) + '</span>'); 7815 7816 // loop through words to determine which word the 7817 // ellipsis container should start from (need to 7818 // re-query spans from DOM so we can get their offset) 7819 base.$el.find('span').each(setStartEllipAt); 7820 7821 // startEllipAt could be 0 so make sure we're 7822 // checking undefined instead of falsey 7823 if (startEllipAt != null) { 7824 updateText(startEllipAt); 7825 } 7826 } 7827 7828 /** 7829 * updateText() updates the text in the DOM 7830 * with a span around the line that needs 7831 * to be truncated 7832 * 7833 * @param {Number} i 7834 */ 7835 function updateText(nth) { 7836 // add a span that wraps from nth 7837 // word to the end of the string 7838 words[nth] = '<span class="' + base.opts.ellipLineClass + '">' + words[nth]; 7839 words.push('</span>'); 7840 7841 // update the DOM with 7842 // our new string/markup 7843 base.$el.html(words.join(' ')); 7844 } 7845 7846 function escapeText(text){ 7847 return String(text).replace(/[&<>"'``\/]/g, function (s) { 7848 return htmlEntities[s]; 7849 }); 7850 } 7851 7852 // only define the method if it's required 7853 if (base.opts.lines === 'auto') { 7854 7855 /** 7856 * setStartEllipByHeight() sets the start 7857 * position to the first word in the last 7858 * line of the element that doesn't overflow 7859 * 7860 * @param {Number} i 7861 * @param {Node} word 7862 */ 7863 var setStartEllipByHeight = function(i, word) { 7864 var $word = $(word), 7865 top = $word.position().top; 7866 7867 lineHeight = lineHeight || $word.height(); 7868 7869 if (top === currOffset) { 7870 // if it's top matches currOffset 7871 // then it's on the same line 7872 // as the previous word 7873 lines[currLine].push($word); 7874 } else { 7875 // otherwise we're 7876 // on a new line 7877 currOffset = top; 7878 currLine += 1; 7879 lines[currLine] = [$word]; 7880 } 7881 7882 // if the bottom of the word is outside 7883 // the element (overflowing) then 7884 // stop looping and set startEllipAt to 7885 // the first word in the previous line 7886 if (top + lineHeight > contHeight) { 7887 startEllipAt = i - lines[currLine - 1].length; 7888 return false; 7889 } 7890 }; 7891 7892 setStartEllipAt = setStartEllipByHeight; 7893 } 7894 7895 // only define the method if it's required 7896 if (typeof base.opts.lines === 'number' && base.opts.lines > 1) { 7897 7898 /** 7899 * setStartEllipByLine() sets the start 7900 * position to the first word in the line 7901 * that was passed to opts. This forces 7902 * the ellipsis on a specific line 7903 * regardless of overflow 7904 * 7905 * @param {Number} i 7906 * @param {Node} word 7907 */ 7908 var setStartEllipByLine = function(i, word) { 7909 var $word = $(word), 7910 top = $word.position().top; 7911 7912 // if top isn't currOfset 7913 // then we're on a new line 7914 if (top !== currOffset) { 7915 currOffset = top; 7916 currLine += 1; 7917 } 7918 7919 // if the word's currLine is equal 7920 // to the line limit passed via options 7921 // then start ellip from this 7922 // word and stop looping 7923 if (currLine === base.opts.lines) { 7924 startEllipAt = i; 7925 return false;
7926 } 7927 }; 7928 7929 setStartEllipAt = setStartEllipByLine; 7930 } 7931 7932 // only bind to window resize if required 7933 if (base.opts.responsive) { 7934 7935 /** 7936 * resize() resets necessary vars 7937 * and content and then re-initialises 7938 * the Ellipsis script 7939 */ 7940 var resize = function() { 7941 lines = []; 7942 currLine = 0; 7943 currOffset = null; 7944 startEllipAt = null; 7945 base.$el.html(escapeText(base.text)); 7946 7947 clearTimeout(resizeTimer); 7948 resizeTimer = setTimeout(init, 100); 7949 }; 7950 7951 $(window).on('resize.' + namespace, resize); 7952 } 7953 7954 // start 'er up 7955 create(); 7956 } 7957 7958 $.fn[namespace] = function(opts) { 7959 return this.each(function() { 7960 try { 7961 $(this).data(namespace, (new Ellipsis(this, opts))); 7962 } catch (err) { 7963 if (window.console) { 7964 console.error(namespace + ': ' + err); 7965 } 7966 } 7967 }); 7968 }; 7969 7970})); 7971/** 7972 * Created by yzheng on 2/1/16. 7973 */ 7974 7975 7976$(function() { 7977 7978 $.fn.news_center=function(){ 7979 // we only allow object parameter 7980 var options; 7981 if(arguments.length==0){ 7982 // using default value 7983 options = DEFAULT_OPTIONS; 7984 }else if(typeof arguments[0]==="object"){ 7985 options = $.extend({}, DEFAULT_OPTIONS, arguments[0]); 7986 }else { 7987 throw TypeError('Unsupported arguments for news center'); 7988 } 7989 $.each(this,function(){ 7990 initialize($(this),options); 7991 }); 7992 7993 }; 7994 /** 7995 * Initialize the the news center by making an ajax call to grab the data 7996 * @param selector 7997 * @param options 7998 */ 7999 function initialize(selector,options){ 8000 var fallbackImage = selector.data('fallbackImage'); 8001 var jsonVersion = selector.data('jsonVersion'); 8002 //we make the ajax call 8003 $.ajax({ 8004 url:options.endpoint+"?page="+options.page+"&filter[posts_per_page]="+options.postsPerPage, 8005 dataType:options.dataType, 8006 success:function(data,status){ 8007 if(status==="success"){ 8008 var results=[]; 8009 // meaning we have made a successful call 8010 for(var i=0;i<data.length;i++){ 8011 var news; 8012 if(typeof options.dataParser!=="undefined" && typeof options.dataParser ==="function"){ 8013 if(jsonVersion === 1) { 8014 news = options.dataParser(data[i]); 8015 } 8016 else { 8017 news = options.dataParserVersion2(data[i]); 8018 } 8019 }else{ 8020 if (jsonVersion === 2) { 8021 news = dataParserVersion2(data[i]); 8022 } else { 8023 if(jsonVersion === 3) { 8024 news = dataParserVersion3(data[i]); 8025 } else { 8026 news = dataParser(data[i]); 8027 } 8028 } 8029 } 8030 8031 8032 8033 results.push(news); 8034 } 8035 if(results.length>0) { 8036 var renderItem; 8037 if (typeof options.builder !== "undefined" && typeof options.builder === "function") { 8038 renderItem = options.builder; 8039 } else { 8040 renderItem = builder; 8041 } 8042 var renderOptions = { 8043 news_dataSet: results, 8044 fallbackImage: fallbackImage 8045 } 8046 renderItem(selector, renderOptions, options); 8047 } 8048 selector.find(options.spinner).hide(); 8049 $('.news--article--headline').ellipsis({ lines: 4 }); 8050 } 8051 } 8052 }); 8053 8054 } 8055 8056 /** 8057 * 8058 * @type {{endpoint: string, page: number, postsPerPage: number, hbtpl: string, blockCount: number}} 8059 */ 8060 DEFAULT_OPTIONS={ 8061 endpoint:"https://alabamanewscenter.com/wp-json/posts", 8062 page:1, 8063 postsPerPage:3, 8064 hbtpl:"#news-template", 8065 blockCount:3 8066 } 8067 /** 8068 * Default Builder to use if none is pass in 8069 * @param selector 8070 * @param results 8071 * @param options 8072 */ 8073 function builder(selector,results,options){ 8074 //we need to check the post per page 8075 var endIndex=options.blockCount; 8076 if(options.postsPerPage===3){ 8077 $newsRow = buildTemplate(options,results);// pass in the result object to let the handle bar take care the rest 8078 selector.append($newsRow); 8079 }else{ 8080 // meaning we need to slice 8081 for(var j=1;j<=(options.postsPerPage/options.blockCount);j++){ 8082 var slicingIndex = (j-1)*options.blockCount; 8083 var slicedDataSet = results.news_dataSet; 8084 var data = slicedDataSet.slice(slicingIndex,endIndex); 8085 var curNewsRow = buildTemplate(options,{ 8086 news_dataSet : data, 8087 fallbackImage: results.fallbackImage 8088 }); 8089 if(typeof curNewsRow !=="undefined") { 8090 selector.append(curNewsRow); 8091 } 8092 endIndex=endIndex*2; 8093 } 8094 } 8095 } 8096 8097 /** 8098 * Default parser to use if none is pass in 8099 * @param news 8100 * @returns {{title: (title|*|string|string), link: (link|*|string), imageSrc: (*|string|$.validator.classRuleSettings.url|{url}|boolean|$.validator.methods.url), categoryName: *, categorySlug: *, dateRaw: (string|$.validator.classRuleSettings.date|{date}|boolean|$.validator.methods.date|Function|*), dateFormated: *}} 8101 */ 8102 function dataParser(news){ 8103 var title = $('<p />').html(news.title).text(); 8104 var link = news.link; 8105 var picture = news.featured_image && 8106 news.featured_image.attachment_meta && 8107 news.featured_image.attachment_meta.sizes && 8108 news.featured_image.attachment_meta.sizes.medium && 8109 news.featured_image.attachment_meta.sizes.medium.url; 8110 var categories = news.terms.category; 8111 var date = news.date; 8112 if(typeof categories !=="undefined"){ 8113 if(categories.length>0){ 8114 var categoryName = categories[0].name; 8115 var categorySlug = categories[0].slug; 8116 } 8117 } 8118 return {title:title,link:link,imageSrc:picture, 8119 categoryName:categoryName,categorySlug:categorySlug, 8120 dateRaw:date,dateFormated:formatDate(new Date(date))}; 8121 8122 } 8123 8124 /** 8125 * Default parser to use if none is pass in 8126 * @param news 8127 * @returns {{title: (title|*|string|string), link: (link|*|string), imageSrc: (*|string|$.validator.classRuleSettings.url|{url}|boolean|$.validator.methods.url), categoryName: *, categorySlug: *, dateRaw: (string|$.validator.classRuleSettings.date|{date}|boolean|$.validator.methods.date|Function|*), dateFormated: *}} 8128 */ 8129 function dataParserVersion2(news){ 8130 var title = news.acf && news.acf.teaser__short_title ? news.acf.teaser__short_title : $('<p />').html(news.title.rendered).text(); 8131 var link = news.link; 8132 var picture = news.featured_image_src; 8133 var categoryName = news.categories_names ? news.categories_names[0] : null; 8134 var categorySlug = news.slug; 8135 var date = news.date; 8136 return {title:title,link:link,imageSrc:picture, 8137 categoryName:categoryName,categorySlug:categorySlug, 8138 dateRaw:date,dateFormated:formatDate(new Date(date))}; 8139 8140 } 8141 function dataParserVersion3(news){ 8142 var title = news.acf && news.acf.teaser__short_title ? news.acf.teaser__short_title : $('<p />').html(news.title.rendered).text(); 8143 var link = news.link; 8144 var picture = news.jetpack_featured_media_url; 8145 var categoryName = news.categories_names ? news.categories_names[0] : null; 8146 var categorySlug = news.slug; 8147 var date = news.date; 8148 return {title:title,link:link,imageSrc:picture, 8149 categoryName:categoryName,categorySlug:categorySlug, 8150 dateRaw:date,dateFormated:formatDate(new Date(date))}; 8151 8152 } 8153 8154 /** 8155 * Build template based on handlebars template implementation 8156 * @param options 8157 * @param news 8158 * @returns {*|{options, files}|{files}} 8159 */ 8160 8161 function buildTemplate(options,news){ 8162 var source = $(options.hbtpl).html(); 8163 if(source && source.length!==0) { 8164 var template = Handlebars.compile(source); 8165 return template(news); 8166 } 8167 8168 } 8169 8170 /** 8171 * Helper function to format the date on MMMM dd, yyyyy 8172 * @param date 8173 * @returns {string} 8174 */ 8175 function formatDate(date){ 8176 var monthNames = [ 8177 "January", "February", "March", 8178 "April", "May", "June", "July", 8179 "August", "September", "October", 8180 "November", "December" 8181 ]; 8182 var dateFormated = monthNames[date.getMonth()]+" "+date.getDate()+", "+date.getFullYear(); 8183 return dateFormated; 8184 } 8185}); 8186 8187$(function(){ 8188 $(".news-container").news_center({postsPerPage:$("#postPerPage").val(),endpoint:$("#endPoint").val(),spinner:".spinner"}); 8189}); 8190 8191$(function () { 8192 $('.more-article-data').each(function () { 8193 var data = $(this); 8194 var root = $(this); 8195 8196 $.get('/bin/southern/global/latestarticles', { 8197 tags: data.attr('data-tags'), 8198 path: data.attr('data-path'), 8199 limit: 4, 8200 filter: 'most-recent', 8201 start: 0, 8202 }).done(function (data) { 8203 var pages = data.pages; 8204 pages = pages 8205 .filter(function (s) { 8206 return s.path != window.location.href; 8207 }) 8208 .slice(0, 3); 8209 8210 for (var i = 0; i < pages.length; i++) { 8211 root.append( 8212 '<li><a href="' + pages[i].path + '">' + pages[i].title + '</a></li>' 8213 ); 8214 } 8215 }); 8216 }); 8217}); 8218 8219/*! 8220 * clipboard.js v2.0.6 8221 * https://clipboardjs.com/ 8222 * 8223 * Licensed MIT © Zeno Rocha 8224 */ 8225!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exp
8225orts.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,a=o.length;i<a;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=n,t.exports.TinyEmitter=n},function(t,e,n){var d=n(3),h=n(4);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!d.string(e))throw new TypeError("Second argument must be a String");if(!d.fn(n))throw new TypeError("Third argument must be a Function");if(d.node(t))return s=e,f=n,(u=t).addEventListener(s,f),{destroy:function(){u.removeEventListener(s,f)}};if(d.nodeList(t))return a=t,c=e,l=n,Array.prototype.forEach.call(a,function(t){t.addEventListener(c,l)}),{destroy:function(){Array.prototype.forEach.call(a,function(t){t.removeEventListener(c,l)})}};if(d.string(t))return o=t,r=e,i=n,h(document.body,o,r,i);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,a,c,l,u,s,f}},function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e,n){var a=n(5);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=a(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},function(t,e){if("undefined"!=typeof Element&&!Element.prototype.matches){var n=Element.prototype;n.matches=n.matchesSelector||n.mozMatchesSelector||n.msMatchesSelector||n.oMatchesSelector||n.webkitMatchesSelector}t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}},function(t,e,n){"use strict";n.r(e);var o=n(0),r=n.n(o),i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};function a(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function c(t){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,c),this.resolveOptions(t),this.initSelection()}var l=(function(t,e,n){return e&&a(t.prototype,e),n&&a(t,n),t}(c,[{key:"resolveOptions",value:function(t){var e=0<arguments.length&&void 0!==t?t:{};this.action=e.action,this.container=e.container,this.emitter=e.emitter,this.target=e.target,this.text=e.text,this.trigger=e.trigger,this.selectedText=""}}
8225,{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=r()(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=r()(this.target),this.copyText()}},{key:"copyText",value:function(){var e=void 0;try{e=document.execCommand(this.action)}catch(t){e=!1}this.handleResult(e)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(t){var e=0<arguments.length&&void 0!==t?t:"copy";if(this._action=e,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":i(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),c),u=n(1),s=n.n(u),f=n(2),d=n.n(f),h="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},p=function(t,e,n){return e&&y(t.prototype,e),n&&y(t,n),t};function y(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}var m=(function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(v,s.a),p(v,[{key:"resolveOptions",value:function(t){var e=0<arguments.length&&void 0!==t?t:{};this.action="function"==typeof e.action?e.action:this.defaultAction,this.target="function"==typeof e.target?e.target:this.defaultTarget,this.text="function"==typeof e.text?e.text:this.defaultText,this.container="object"===h(e.container)?e.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=d()(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return b("action",t)}},{key:"defaultTarget",value:function(t){var e=b("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return b("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(t){var e=0<arguments.length&&void 0!==t?t:["copy","cut"],n="string"==typeof e?[e]:e,o=!!document.queryCommandSupported;
8225return n.forEach(function(t){o=o&&!!document.queryCommandSupported(t)}),o}}]),v);function v(t,e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,v);var n=function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,(v.__proto__||Object.getPrototypeOf(v)).call(this));return n.resolveOptions(e),n.listenClick(t),n}function b(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}e.default=m}],r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=6).default;function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}var n,o}); 8226(function () { 8227 var $popup = $('.modal__mobile-popup'); 8228 var arrowHeight = 8; 8229 var facebookTemplate = "https://www.facebook.com/sharer/sharer.php?u="; 8230 var twitterTemplate = "https://twitter.com/share?status=" 8231 var linkedInTemplate = "https://www.linkedin.com/sharing/share-offsite/?url=" 8232 var recaptchaTemplate = '<div id="modal--recaptcha" class=" g-recaptcha" data-sitekey="{sitekey}"></div>' 8233 function setArrowButtonHeight() { 8234 $(".modal__mobile-arrow").css("bottom", $popup.height()-arrowHeight); 8235 } 8236 8237 8238 8239 $(document).ready(function(){ 8240 8241 $(window).resize(function(){ 8242 $(window.location.hash).closest(".modal__mobile-arrow").css("bottom", $popup.height()-arrowHeight); 8243 }); 8244 8245 if(window.location.hash) { 8246 $(window.location.hash).closest(".modal__container").show(); 8247 $(window.location.hash).closest(".modal__container").css("margin-bottom", "0"); 8248 setTimeout(function() { 8249 setupRecaptcha() 8250 },1000) 8251 } 8252 8253 $('.modal__mobile-arrow').click(function(){ 8254 8255 $(this).toggleClass("modal__mobile-arrow--open"); 8256 $(this).css("bottom", $(this).parent().find(".modal__mobile-popup").height()-arrowHeight); 8257 8258 }); 8259 8260 $('.modal__close').click(function(){ 8261 $(this).closest(".modal__container").hide(); 8262 var noHashURL = window.location.href.replace(/#.*$/, ''); 8263 window.history.replaceState('', document.title, noHashURL) 8264 $(".email-validation-error").removeClass("show-validation"); 8265 $(".email-validation-mobile-error").removeClass("show-validation"); 8266 $(".recaptcha-validation-error").removeClass("show-validation"); 8267 $(".recaptcha-mobile-validation-error").removeClass("show-validation"); 8268 8269 //turn off any error 8270 }); 8271 8272 $('.js-copy').click(function (e) { 8273 e.preventDefault(); 8274 var copyText = window.location.href; 8275 8276 8277 var dummy = document.createElement('input'), 8278 text = window.location.href; 8279 8280 document.body.appendChild(dummy); 8281 dummy.value = text; 8282 dummy.select(); 8283 document.execCommand('copy'); 8284 document.body.removeChild(dummy); 8285 8286 $(this).tooltip({ 8287 content: "Copied!" 8288 }); 8289 8290 }); 8291 8292 $('.js-copy').mouseleave(function (e) { 8293 $(this).tooltip( "close" ); 8294 $(this).tooltip({ 8295 content: "Copy?" 8296 }); 8297 }); 8298 }); 8299 8300 $( window ).on('hashchange', function( e ) { 8301 8302 if(window.location.hash) { 8303 $(window.location.hash).closest(".modal__container").show(); 8304 $(window.location.hash).closest(".modal__container").css("margin-bottom", "0"); 8305 } 8306 setupRecaptcha() 8307 8308 }); 8309 8310 function setupRecaptcha() 8311 {
8312 $('#modal--recaptcha').remove(); 8313 if(!checkModalCookie() && $('.modal__content').length) 8314 { 8315 if (window.innerWidth >= 992) { 8316 var hashTemplate = recaptchaTemplate.replace("{siteKey}", $(window.location.hash).data("siteKey")) 8317 $(window.location.hash).find(".recaptcha").before(hashTemplate) 8318 } 8319 else { 8320 $(window.location.hash).find(".recaptcha--mobile").before(hashTemplate) 8321 } 8322 if(window.grecaptcha) 8323 { 8324 window.grecaptcha.render("modal--recaptcha", { 8325 sitekey: "6LcF28YUAAAAAOpvSk72p8oHRBGvYWB-mAavWHG-" 8326 }); 8327 } 8328 8329 } 8330 if(checkModalCookie()) { 8331 $('.modal__container .g-recaptcha').remove(); 8332 $('.modal__container input').remove(); 8333 $('.modal__container label').remove(); 8334 } 8335 } 8336 8337 8338 8339 8340 8341 $(document).ready(function() { 8342 $('[data-toggle="tooltip"]').tooltip() 8343 $('.js-share').click(function(e) { 8344 e.preventDefault(); 8345 var href = window.location.href.replace('#', '%23'); 8346 var url = "" ; 8347 var title = $(this).closest('.modal__container').find('.js-title').text(); 8348 var description = $(this).closest('.modal__container').find('.js-description').text(); 8349 if($(this).hasClass('js-facebook')) 8350 { 8351 url += facebookTemplate; 8352 url += href; 8353 } 8354 else if($(this).hasClass('js-linkedIn')) 8355 { 8356 url += linkedInTemplate; 8357 url += href; 8358 url += ("&title=" + title); 8359 url += ("&summary=" + description); 8360 } 8361 else if($(this).hasClass('js-twitter')) 8362 { 8363 url += twitterTemplate; 8364 url += href; 8365 url += ("&text=" + title); 8366 } 8367 8368 window.open(url, 'fbShareWindow', 'height=450, width=550, top=' + ($(window).height() / 2 - 275) + ', left=' + ($(window).width() / 2 - 225) + ', toolbar=0, location=0, menubar=0, directories=0, scrollbars=0'); 8369 return false; 8370 }); 8371 }); 8372 8373 $(document).ready(function() { 8374 var ipAddress; 8375 if($('.require-email-download').length > 0) 8376 { 8377 $.get('https://ipinfo.io/json?token=a142ff36811fed', function(data) { 8378 ipAddress = data.ip; 8379 }); 8380 } 8381 8382 $('.require-email-download').click(function(e) { 8383 e.preventDefault(); 8384 if(checkModalCookie()) 8385 { 8386 window.location = $(this).closest('.modal__container').data('path'); 8387 } 8388 else { 8389 var anchor = $(this).attr('anchor'); 8390 var path = getResourcePath("#require-email-download-" + anchor); 8391 var email = validateEmail("require-email-download-input-" + anchor, ".email-validation-error"); 8392 if (email == '') { 8393 return; 8394 } 8395 var recaptcha = validateRecaptcha(".recaptcha-validation-error"); 8396 if (recaptcha == '') { 8397 return; 8398 } 8399 8400 //call servlet 8401 callDownloader(path, email, recaptcha); 8402 } 8403 8404 }); 8405 8406 8407 $('.require-email-mobile-download').click(function(e) { 8408 e.preventDefault(); 8409 var recaptcha = $("#g-recaptcha-response").val(); 8410 var anchor = $(this).attr('anchor'); 8411 var path = getResourcePath("#require-email-download-" + anchor); 8412 var email = validateEmail("require-email-mobile-download-input-" + anchor, ".email-validation-mobile-error"); 8413 8414 if (email == '') { 8415 return; 8416 } 8417 var recaptcha = validateRecaptcha(".recaptcha-mobile-validation-error"); 8418 if (recaptcha == '') { 8419 return; 8420 } 8421 //call servlet 8422 callDownloader(path, email, recaptcha); 8423 }); 8424 8425 function getResourcePath(resourceHook) { 8426 return [$(resourceHook).attr("parentPath"), $(resourceHook).attr("childPath")]; 8427 } 8428 8429 function validateEmail(inputId, errorClass) { 8430 var inputEmail = document.getElementById(inputId).value; 8431 if (inputEmail.toLowerCase().match( 8432 /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 8433 )) { 8434 $(errorClass).removeClass("show-validation"); 8435 //make sure to remove when modal is closed 8436 return inputEmail; 8437 } else { 8438 $(errorClass).addClass("show-validation"); 8439 return ''; 8440 } 8441 } 8442 8443 function validateRecaptcha(errorClass) { 8444 var recaptcha = window.grecaptcha.getResponse(); 8445 if (recaptcha != null && recaptcha != '') { 8446 $(errorClass).removeClass("show-validation"); 8447 return recaptcha; 8448 } else { 8449 $(errorClass).addClass("show-validation"); 8450 return ''; 8451 } 8452 8453 } 8454 8455 function callDownloader(path, email, token) { 8456 //parent - component path 8457 //child - individual modal in this set of modals 8458 //email - of input 8459 //ipAddress 8460 //recaptcha token 8461 var url = '/bin/southern/global/downloadasset.json?' + 8462 'parentPath=' + path[0] + 8463 '&childPath=' + path[1] + 8464 '&email=' + email + 8465 '&ipAddress=' + ipAddress +
8466 '&token=' + token; 8467 $.get(url, function (data, status) { 8468 if (data.assetReturned) { 8469 createModalCookie(); 8470 setupRecaptcha() 8471 window.location = data.asset; 8472 8473 } else { 8474 console.log("Download Modal Servlet failed. See logs.") 8475 } 8476 }); 8477 8478 } 8479 }); 8480 function checkModalCookie() 8481 { 8482 var cookieString = 'modalEmailAcquired'; 8483 return document.cookie.indexOf(cookieString) > -1; 8484 } 8485 8486 function createModalCookie() 8487 { 8488 8489 var cookieString = 'modalEmailAcquired'; 8490 //1 year 8491 var expirationDate = new Date(); 8492 expirationDate = new Date(expirationDate.getTime() +1000*60*60*24*365); 8493 document.cookie = 'modal='+cookieString+'; expires='+expirationDate.toGMTString()+';'; 8494 } 8495}()); 8496var totalMarketingSwipers = []; 8497$(function () { 8498 function swiperSwitchBoard(index, active) { 8499 var swiper = '.swiper' + (index + 1); 8500 var next = '.next' + index; 8501 var prev = '.prev' + index; 8502 var swiper = new Swiper(swiper, { 8503 slidesPerView: 'auto', 8504 simulateTouch: true, 8505 resizeReInit: true, 8506 initialSlide: active, 8507 keyboard: { 8508 enabled: true, 8509 }, 8510 navigation: { 8511 nextEl: next, 8512 prevEl: prev, 8513 }, 8514 freeMode: { 8515 enabled: true, 8516 sticky: true, 8517 }, 8518 }); 8519 totalMarketingSwipers.push(swiper); 8520 } 8521 8522 function checkSliderButtons(currentTabList) { 8523 if (currentTabList[0].offsetWidth > currentTabList.parent()[0].offsetWidth) { 8524 var prevButton = currentTabList.closest('.marketing-tab__tab-container').find(".swiper-button-prev-custom"); 8525 var nextButton = currentTabList.closest('.marketing-tab__tab-container').find(".swiper-button-next-custom"); 8526 $(prevButton[0]).css("display", "flex"); 8527 $(nextButton[0]).css("display", "flex"); 8528 8529 return true; 8530 } else { 8531 var prevButton = currentTabList.closest('.marketing-tab__tab-container').find(".swiper-button-prev-custom"); 8532 var nextButton = currentTabList.closest('.marketing-tab__tab-container').find(".swiper-button-next-custom"); 8533 $(prevButton[0]).css("display", "none"); 8534 $(nextButton[0]).css("display", "none"); 8535 return false; 8536 } 8537 } 8538 8539 function createSwipers() { 8540 var tabs = $('.marketing-tab'); 8541 var numberOfSwipers = []; 8542 tabs.each(function(index, tab) { 8543 var rightCheck = $(tab).find(".marketing-tab--right"); 8544 var leftCheck = $(tab).find(".marketing-tab--left"); 8545 var topCheck = $(tab).find(".marketing-tab--top"); 8546 //todo add centered tab options here 8547 8548 var parent = $(tab).find("ol"); 8549 var activeTab = $(tab).find(".cmp-tabs__tab--active"); 8550 if (activeTab.length == 0) { 8551 activeTab = parent.children()[0]; 8552 } 8553 8554 updateActiveTab(activeTab); 8555 setSingleTabColor(activeTab); 8556 updateActivePanel(activeTab, 0); 8557 if (((rightCheck.length == 1 || leftCheck.length == 1) && window.innerWidth < 990) || topCheck.length == 1) { 8558 if (checkSliderButtons(parent)) { 8559 var swiperLevel = $(tab).find(".swiper-container"); 8560 var nextButton = $(tab).find(".swiper-button-next-custom"); 8561 var prevButton = $(tab).find(".swiper-button-prev-custom"); 8562 activeTab = $(activeTab).index(); 8563 nextButton.addClass("next" + (index)); 8564 prevButton.addClass("prev" + (index)); 8565 swiperLevel.addClass("swiper" + (index + 1)); 8566 swiperSwitchBoard(index, activeTab); 8567 } 8568 } else { 8569 checkSliderButtons(parent); 8570 } 8571 }); 8572 } 8573 8574 $(".marketing-tab .swiper-slide").click(function () { 8575 var parent = $(this).parent(); 8576 var previouslyActive = $(parent).attr("active-tab"); 8577 $(parent[0].children[previouslyActive]).css({ "border-bottom": "4px s
8577olid " + "transparent" }); 8578 setSingleTabColor(this); 8579 updateActiveTab(this); 8580 updateActivePanel(this, previouslyActive); 8581 8582 }); 8583 8584 $(".marketing-tab .swiper-slide").keydown(function (key) { 8585 //update bot coloring active tab and swiper 8586 var parent = $(this).parent()[0]; 8587 var currentActive = $(parent).attr("active-tab"); 8588 var children = $(parent)[0].children; 8589 if (key.keyCode == 37 || key.keyCode == 38) { 8590 if (currentActive != 0) { 8591 $(children[currentActive]).css({ "border-bottom": "4px solid " + "transparent" }); 8592 var nextKey = $($(parent)[0].children[currentActive - 1]); 8593 setSingleTabColor(nextKey); 8594 updateActiveTab(nextKey); 8595 updateActivePanel(nextKey, currentActive); 8596 } 8597 } 8598 8599 if (key.keyCode == 39 || key.keyCode == 40) { 8600 if (currentActive != children.length - 1) { 8601 $(children[currentActive]).css({ "border-bottom": "4px s
8601olid " + "transparent" }); 8602 var nextKey = $($(parent)[0].children[currentActive - -1]); 8603 setSingleTabColor(nextKey); 8604 updateActiveTab(nextKey); 8605 updateActivePanel(nextKey, currentActive); 8606 } 8607 } 8608 }); 8609 8610 function hideAllContent() { 8611 if ($(".wcmmode-title-only").length == 0) { 8612 $(".marketing-tab--content").each(function (index, item) { 8613 $(item).css("display", "none"); 8614 }) 8615 } 8616 } 8617 8618 function setSingleTabColor(tab) { 8619 var colorCheck = $(tab).closest('.cmp-tabs.marketing-tab'); 8620 var color = colorCheck[0].className.includes("dark") ? $(colorCheck[0]).attr("active-tab-color--dark") : $(colorCheck[0]).attr("active-tab-color--light"); 8621 $(tab).css({ "border-bottom": "4px solid " + color }); 8622 } 8623 8624 function updateActiveTab(tab) { 8625 var ol = $(tab).parent(); 8626 $(ol[0]).attr("active-tab", $(tab).index()); 8627 } 8628 8629 function updateActivePanel(tab, oldIndex) { 8630 var wrapper = $(tab).closest('.marketing-tab--layer').find(".bootstrap-content-wrapper"); 8631 $($(wrapper[0]).children()[oldIndex]).css("display", "none"); 8632 $($(wrapper[0]).children()[$(tab).index()]).css("display", "block"); 8633 } 8634 8635 function destroySwipers() { 8636 while(totalMarketingSwipers.length > 0) { 8637 totalMarketingSwipers.shift().destroy(); 8638 } 8639 } 8640 8641 function adjustMarketingTabs() { 8642 destroySwipers(); 8643 createSwipers(); 8644 } 8645 8646 hideAllContent(); 8647 destroySwipers(); 8648 createSwipers(); 8649 8650 window.addEventListener('resize', adjustMarketingTabs); 8651 window.addEventListener("orientationchange", adjustMarketingTabs); 8652 8653}); 8654 8655 8656 8657$(function () { 8658 8659 function search(zipcode, component) { 8660 if (window.lookup.includes(zipcode)) { 8661 component.find(".results").show(); 8662 component.find(".no-results").hide(); 8663 } else { 8664 component.find(".results").hide(); 8665 component.find(".no-results").show(); 8666 } 8667 } 8668 8669 $(".lookup-form .btn").click(function (event) { 8670 event.preventDefault(); 8671 console.log($(this)) 8672 search($(this).prev().val(), $(this).closest('.lookup-form')); 8673 }); 8674 8675 $(".lookup-form input").keydown(function (event) { 8676 if (event.which == 13) { 8677 search($(this).val(), $(this).closest('.lookup-form')); 8678 } 8679 }); 8680}); 8681$(function () { 8682 var width = $(window).width(); 8683 var height = $(window).height(); 8684 var $itemGrid = $('.item-grid'); 8685 var itemGrid = $('.item-grid'); 8686 var mobile = $('.item-grid').data('mobile'); 8687 var tablet = $('.item-grid').data('tablet'); 8688 var desktop = $('.item-grid').data('desktop'); 8689 var style = "margin"; 8690 var style2 = "margin-bottom"; 8691 var widths = [768, 992, 1200]; 8692 8693 $(document).ready(function(){ 8694 itemGridAdjustMarginBasedOnPathWidth() 8695 $(document).resize(itemGridAdjustMarginBasedOnPathWidth()); 8696 $(window).resize(itemGridAdjustMarginBasedOnPathWidth()); 8697 }); 8698 8699 8700 8701 function itemGridAdjustMarginBasedOnPathWidth() { 8702 reset(); 8703 $itemGrid.each(function(index) { 8704 itemGrid = $(this); 8705 mobile = itemGrid.data('mobile'); 8706 tablet = itemGrid.data('tablet'); 8707 desktop = itemGrid.data('desktop'); 8708 if(width < widths[0]) 8709 { 8710 itemGridAdjustMargin(mobile); 8711 } 8712 else if(width < widths[1]) 8713 { 8714 itemGridAdjustMargin(tablet); 8715 } 8716 else { 8717 itemGridAdjustMargin(desktop); 8718 } 8719 }); 8720 } 8721 8722 function reset() { 8723 width = $(window).width(); 8724 height = $(window).height(); 8725 $itemGrid.find('.item-grid__single').each(function(index) { 8726 $(this).find('.item-grid__link-container').css(style, ""); 8727 $(this).css(style2, ""); 8728 }); 8729 } 8730 8731 function itemGridAdjustMargin(itemGridNum) { 8732 var itemGridSingles = itemGrid.find('.item-grid__single'); 8733 var rows = Math.ceil(itemGridSingles.length/itemGridNum); 8734 itemGridSingles.each(function(index) { 8735 var currentRow = Math.ceil((index+1)/itemGridNum); 8736 $(this).find('.item-grid__link-container').css(style, "0 10px"); 8737 if(index >= itemGridSingles.length - itemGridNum && rows === currentRow) 8738 { 8739 $(this).css(style2, "0"); 8740 } 8741 }) 8742 } 8743 8744 window.onresize = itemGridAdjustMarginBasedOnPathWidth; 8745 8746}); 8747(function($) { 8748 /** jQuery plugin for category-based content filtering 8749 * 8750 * Usage: 8751 * <div class="item-filter"> 8752 * <div class="item-filter--title">Filter the Things</div> 8753 * <form action="" class="item-filter--search"> 8754 * <input name="q"> 8755 * </form> 8756 * <ul> 8757 * <li class="filter-selector--all checked">All</li> 8758 * <li class="filter-selector--category checked" data-tag-slug="things">Things</li> 8759 * <li class="filter-selector--category checked" data-tag-slug="stuff">Stuff</li> 8760 * <li class="filter-selector--category checked" data-tag-slug="whoozits">Whoozits</li> 8761 * </ul> 8762 * <ul class="filterable"> 8763 * <li class="filterable--item visible" data-tag-slugs="things stuff">Something</li> 8764 * <li class="filterable--item visible" data-tag-slugs="things">Nothing</li> 8765 * <li class="filterable--item visible" data-tag-slugs="whoozits">Everything</li> 8766 * </ul> 8767 * <div class="item-filter--close">Close</div> 8768 * </div> 8769 *
8770 * An item_filter manages a bunch of categories and a bunch of categorized 8771 * items. It adds "visible" classes to items that should be visible based 8772 * on which categories have the "checked" class. You can use an optional 8773 * "all" selection to check/uncheck all categories: It follows along 8774 * sensibly as categories are individually checked and unchecked. When 8775 * "all" is checked, all items are visible, even those that have no 8776 * categories. 8777 */ 8778 8779 // Single point of entry for the item_filter plugin. Individual actions are 8780 // described inside this function. 8781 // $(...).item_filter() 8782 // $(...).item_filter({...}) 8783 // $(...).item_filter('toggle_all'); 8784 // $(...).item_filter('toggle_category', category_node); 8785 // $(...).item_filter('toggle_category', 'category-slug'); 8786 // $(...).item_filter('select_category', category_node); 8787 // $(...).item_filter('select_category', 'category-slug'); 8788 // $(...).item_filter('refresh'); 8789 // $(...).item_filter('submit_search'); 8790 $.fn.item_filter = function() { 8791 8792 if (arguments.length == 0) { 8793 8794 // .item_filter() -> initialize with default options 8795 var options = $.extend({}, DEFAULT_OPTIONS); 8796 this.each(function() { 8797 initialize_filter(this, options); 8798 return this; 8799 }); 8800 8801 } else if (typeof arguments[0] === 'string') { 8802 8803 var action = arguments[0]; 8804 switch (action) { 8805 8806 // .item_filter('toggle_all') -> toggle the 'all' category. 8807 // Turns all categories off or on. 8808 case 'toggle_all': 8809 return toggle_all(this); 8810 break; 8811 8812 // .item_filter('toggle_category', category_node); 8813 // .item_filter('toggle_category', 'category-slug'); 8814 // Toggle a single category. 8815 case 'toggle_category': 8816 return toggle_category(this, arguments[1]); 8817 break; 8818 8819 // .item_filter('select_category', category_node); 8820 // .item_filter('select_category', 'category-slug'); 8821 // Toggle a single category. 8822 case 'select_category': 8823 return select_category(this, arguments[1]); 8824 break; 8825 8826 // .item_filter('refresh') -> refresh visible items 8827 // This happens automatically when categories toggle, but it can also be 8828 // called manually when external code changes categories or the expected 8829 // result of the .extra_match option below. 8830 case 'refresh': 8831 return update_visible_items(this); 8832 break; 8833 8834 // .item_filter('submit_search') -> submit the search form 8835 // This refreshes visible items, and in mobile environments it hides the 8836 // filter. 8837 case 'submit_search': 8838 return submit_search(this); 8839 break; 8840 8841 default: 8842 throw TypeError('Unsupported action for item_filter'); 8843 } 8844 8845 return this; 8846 8847 } else if (typeof arguments[0] == 'object') { 8848 8849 // .item_filter({...}) -> initialize with options 8850 8851 var options = $.extend({}, DEFAULT_OPTIONS, arguments[0]); 8852 return this.each(function() { 8853 initialize_filter(this, options); 8854 }); 8855 8856 } else { 8857 throw TypeError('Unsupported arguments for item_filter'); 8858 } 8859 }; 8860 8861 // Default options for every item_filter object. You can pass an object to 8862 // the item_filter constructor (above) to override these options. 8863 var DEFAULT_OPTIONS = { 8864 // html classes used for tagging nodes 8865 checked_class: 'checked', 8866 open_class: 'open', 8867 visible_class: 'visible', 8868 8869 // jquery data fields for storing category and item tags 8870 category_tag_data: 'tag-slug', 8871 item_tags_data: 'tag-slugs', 8872 8873 // the title, or how to find it ($filter.find(options.title_selector)) 8874 $title: undefined, 8875 title_selector: '.item-filter--title', 8876 8877 // the close button, or how to find it ($filter.find(options.close_selector)) 8878 $close: undefined, 8879 close_selector: '.item-filter--close', 8880 8881 // the search form, or how to find it ($filter.find(options.search_selector)) 8882 $search: undefined, 8883 search_selector: '.item-filter--search', 8884 8885 // the search term field, or how to find it ($filter.find(options.search_field_selector)) 8886 $search_field: undefined, 8887 search_field_selector: '.item-filter__input', 8888 8889 // the items, or how to find them $(options.items_selector) 8890 $items: undefined, 8891 items_selector: '.filterable--item', 8892 8893 // an "all" selection, or how to find it ($filter.find(options.all_selector)) 8894 $all: undefined, 8895 all_selector: '.filter-selector--all', 8896 8897 // the categories, or how to find them ($filter.find(options.categories_selector)) 8898 $categories: undefined, 8899 categories_selector: '.filter-selector--category', 8900 8901 // an optional extra function to further filter visibility 8902 // 8903 // After determining that an item has appropriate categories, item_filter 8904 // calls this function with the item's jquery object as an argument. If 8905 // the function returns true, the item is visible. If it returns false, 8906 // the item is not visible. Use this to incorporate factors other than 8907 // categories into filtering. The default shows all objects. 8908 extra_match: default_extra_match, 8909 8910 // an optional extra function to customize how to collect item search text 8911 // 8912 // When the filter search is active, the filter calls this function on 8913 // each item to find the item's searchable text. The default 8914 // implementation simply returns all text in the item. 8915 item_text: default_item_text 8916 }; 8917 8918 // Initialize an item_filter selector with filterable items 8919 function initialize_filter(selector, options) { 8920 var $selector = $(selector); 8921 console.log("test") 8922 // Find related DOM nodes 8923 if (typeof options.$title === 'undefined') { 8924 options.$title = find_title($selector, options); 8925 } 8926 if (typeof options.$close === 'undefined') { 8927 options.$close = find_close($selector, options); 8928 } 8929 if (typeof options.$search === 'undefined') { 8930 options.$search = find_search($selector, options); 8931 } 8932 if (typeof options.$search_field === 'undefined') { 8933 options.$search_field = find_search_field($selector, options); 8934 } 8935 if (typeof options.$items === 'undefined') { 8936 options.$items = find_items($selector, options); 8937 } 8938 if (typeof options.$all === 'undefined') { 8939 options.$all = find_all($selector, options); 8940 } 8941 if (typeof options.$categories ==='undefined') { 8942 options.$categories = find_categories($selector, options); 8943 } 8944 8945 // If we have a search form, tie it into extra_match. 8946 if (options.$search.length > 0) { 8947 options.extra_match = match_search; 8948 } 8949 8950 // Set options 8951 filter_options($selector, options); 8952 8953 // Set up UI handlers 8954 options.$title.click(function() { 8955 $selector.toggleClass(options.open_class); 8956 return false;
8957 }); 8958 options.$close.click(function() { 8959 $selector.toggleClass(options.open_class); 8960 return false; 8961 }); 8962 options.$search.submit(function() { 8963 $selector.item_filter('submit_search'); 8964 return false; 8965 }); 8966 options.$all.click(function() { 8967 $selector.item_filter('toggle_all'); 8968 return false; 8969 }); 8970 options.$categories.click(function() { 8971 $selector.item_filter('select_category', $(this)); 8972 return false; 8973 }); 8974 } 8975 8976 // Get or set the filter options for a selector 8977 function filter_options($selector) { 8978 if (arguments.length > 1) { 8979 var value = arguments[1]; 8980 return $selector.data('item_filter_options', value); 8981 } else { 8982 return $selector.data('item_filter_options'); 8983 } 8984 } 8985 8986 // Find the title for a filter selector 8987 // 8988 // This is used only if $title isn't specified in construction options. 8989 function find_title($selector, options) { 8990 return $selector.find(options.title_selector); 8991 } 8992 8993 // Find the close button for a filter selector 8994 // 8995 // This is used only if $close isn't specified in construction options. 8996 function find_close($selector, options) { 8997 return $selector.find(options.close_selector); 8998 } 8999 9000 // Find the search form for a filter selector 9001 // 9002 // This is used only if $search isn't specified in construction options. 9003 function find_search($selector, options) { 9004 return $selector.find(options.search_selector); 9005 } 9006 9007 // Find the search term field for a filter selector 9008 // 9009 // This is used only if $search_field isn't specified in construction options. 9010 function find_search_field($selector, options) { 9011 return $selector.find(options.search_field_selector); 9012 } 9013 9014 // Find filterable items for a filter selector 9015 // 9016 // This is used only if $items isn't specified in construction options. 9017 function find_items($selector, options) { 9018 // For now we assume there's only one set of filterable items on the 9019 // page. If there are more than that, either set them explicitly in the 9020 // options or else write some interesting general code here to find them. 9021 return $(options.items_selector); 9022 } 9023 9024 // Find the "all" pseudo-category for a filter selector 9025 // 9026 // This is used only if $all isn't specified in construction options. 9027 function find_all($selector, options) { 9028 return $selector.find(options.all_selector); 9029 } 9030 9031 // Find categories for a filter selector 9032 // 9033 // This is used only if $categories isn't specified in construction options. 9034 function find_categories($selector, options) { 9035 return $selector.find(options.categories_selector); 9036 } 9037 9038 // Toggle the "all" pseudo-category for a filter selector 9039 // 9040 // All regular categories follow the "all" category: If this turns "all" 9041 // on then all categories are toggled on. If this turns "all" off then all 9042 // categories are toggled off. Filtered items are hidden or shown 9043 // according to the current category selection. 9044 function toggle_all($selector) { 9045 var options = filter_options($selector); 9046 if (options.$all.hasClass(options.checked_class)) { 9047 options.$all.removeClass(options.checked_class); 9048 options.$categories.removeClass(options.checked_class); 9049 } else { 9050 options.$all.addClass(options.checked_class); 9051 options.$categories.removeClass(options.checked_class); 9052 } 9053 update_visible_items($selector); 9054 } 9055 9056 // Toggle a single category for a filter selector 9057 // 9058 // Takes a category slug or DOM node. If after toggling all categories are 9059 // on the "all" pseudo-category is also turned on. If any are off then the 9060 // "all" pseudo-category is turned off. Filtered items are hidden or shown 9061 // according to the current category selection. 9062 function toggle_category($selector, category_or_slug) { 9063 var options = filter_options($selector); 9064 var $category = get_category($selector, category_or_slug); 9065 9066 $category.toggleClass(options.checked_class); 9067 update_all($selector); 9068 update_visible_items($selector); 9069 } 9070 9071 // Select a single category in a filter selector 9072 // 9073 // Takes a category slug or DOM node. If "all" is currently selected, then 9074 // this selects that category and deselects others. Otherwise, this 9075 // toggles the category. After updating, if any categories are off then 9076 // the "all" pseudo-category is turned off. Filtered items are hidden or 9077 // shown according to the current category selection. 9078 function select_category($selector, category_or_slug) { 9079 var options = filter_options($selector); 9080 var $category = get_category($selector, category_or_slug); 9081 9082 if (options.$all.hasClass(options.checked_class)) { 9083 options.$categories.removeClass(options.checked_class); 9084 $category.addClass(options.checked_class); 9085 } else { 9086 $category.toggleClass(options.checked_class); 9087 } 9088 update_all($selector); 9089 update_visible_items($selector); 9090 } 9091 9092 // Update the state of the "all" pseudo-category 9093 //
9094 // This is called internally in response to individual categories changing 9095 // to make sure the "all" selector accurately reflects the state of 9096 // selected categories. If all categories are on, turn "all" on. If any 9097 // are off, turn "all" off. 9098 // 9099 // This does not impact visible items. The caller is responsible for that. 9100 function update_all($selector) { 9101 var options = filter_options($selector), 9102 all_checked = true; 9103 options.$categories.each(function() { 9104 if ( ! $(this).hasClass(options.checked_class) ) { 9105 all_checked = false; 9106 } 9107 }); 9108 options.$all.toggleClass(options.checked_class, all_checked); 9109 } 9110 9111 // Update visible items to match the category selection 9112 // 9113 // An item is visible only if any of its tag slugs match a checked 9114 // category. If "all" is checked then all items are visible irrespective 9115 // of categories. If an .extra_match option was passed at construction, it 9116 // will be called for each item that passed category filtering, and that 9117 // function will control visibility. 9118 function update_visible_items($selector) { 9119 var options = filter_options($selector); 9120 9121 options.$items.each(function() { 9122 var $this = $(this), 9123 is_visible = item_visible($this, $selector); 9124 $this.toggleClass(options.visible_class, is_visible); 9125 }); 9126 9127 return $selector; 9128 } 9129 9130 // Handle a user request to submit the integrated search form 9131 // 9132 // This refreshes the visible items. In mobile environments it also hides 9133 // the filter categories. 9134 function submit_search($selector) { 9135 /* Handle search form submission by refreshing the visible items. Because 9136 * of the item_filter extra_match arg at construciton below, this calls 9137 * match_search to evaluate which items are visible. */ 9138 var options = filter_options($selector); 9139 9140 update_visible_items($selector); 9141 $selector.removeClass(options.open_class); 9142 // On some mobile platforms the search cursor remains visible after 9143 // .item-filter is closed. Explicitly blur to work around this. 9144 options.$search_field.blur(); 9145 9146 return $selector; 9147 } 9148 9149 // Is an individual item visible according to the selector? 9150 function item_visible($item, $selector) { 9151 var options = filter_options($selector); 9152 return (item_in_checked_category($item, $selector) && 9153 options.extra_match($selector, $item)); 9154 } 9155 9156 // By default show all items with matching categories. Override this 9157 // behavior in the extra_match constructor option to show only a subset of 9158 // those. 9159 function default_extra_match($selector, $item) { 9160 return true; 9161 } 9162 9163 // Filter visible items by search results 9164 // 9165 // This is an extra_match callback that filters items according to the 9166 // integrated search. The item_filter uses this automatically when a 9167 // search form is present. 9168 // 9169 // Here we do a naive string search to test for match. In an indexed 9170 // implementation we'd probably calculate search results on form submit, 9171 // then in this function just check if a particular question is present. 9172 function match_search($selector, $item) { 9173 var options = filter_options($selector), 9174 raw_term = options.$search_field.val() || '', 9175 term = raw_term.toLowerCase(), 9176 text = options.item_text($item).toLowerCase(); 9177 9178 if (term == '') { 9179 return true; 9180 } 9181 9182 var idx = text.indexOf(term); 9183 return (idx > -1); 9184 } 9185 9186 // Get all the text from an item for use in matching 9187 function default_item_text($item) { 9188 return $item.text(); 9189 } 9190 9191 // Is the item a member of a checked category? 9192 function item_in_checked_category($item, $selector) { 9193 var options = filter_options($selector), 9194 all_checked = options.$all.hasClass(options.checked_class); 9195 9196 if (all_checked) { 9197 return true; 9198 } 9199 9200 var categories = get_checked_categories($selector), 9201 slugs_str = $item.data(options.item_tags_data) || ' ', 9202 slugs = slugs_str.split(' '); 9203 for (var i = 0; i < slugs.length; i++) { 9204 if (categories[slugs[i]]) { 9205 return true; 9206 } 9207 } 9208 return false;
9209 } 9210 9211 // Get slugs for categories that are currently checked 9212 function get_checked_categories($selector) { 9213 var options = filter_options($selector), 9214 categories = {}; 9215 9216 options.$categories.each(function() { 9217 var $this = $(this), 9218 slug = $this.data(options.category_tag_data); 9219 if ($this.hasClass(options.checked_class)) { 9220 categories[slug] = true; 9221 } 9222 }); 9223 9224 return categories; 9225 } 9226 9227 // Given a category node or slug, return a category jq object. Returns 9228 // undefined if category not found. 9229 function get_category($selector, category_or_slug) { 9230 var options = filter_options($selector), 9231 $category; 9232 9233 if (typeof category_or_slug === 'string') { 9234 $category = options.$categories.filter(function() { 9235 return $(this).data(options.category_tag_data) == category_or_slug; 9236 }); 9237 } else { 9238 $category = $(category_or_slug); 9239 } 9240 return $category; 9241 } 9242 9243 $(function() { 9244 $('.item-filter').item_filter(); 9245 var SCREEN_SM = 768 9246 var display = false; 9247 $('.js-item__filters--select').click(function(){ 9248 if(display) 9249 { 9250 $('.item-filter--categories').show(); 9251 $('.item-filter--categories').css("width", $('.category').css("width")); 9252 $('.item_filter--selectbox .selectboxit').css('background', '#E6E6E6') 9253 $('.accordioncomp').css("min-height", '400px'); 9254 display = false; 9255 } 9256 else 9257 { 9258 $('.item-filter--categories').hide(); 9259 $('.item-filter--categories').css("width",''); 9260 $('.accordioncomp').css("min-height", ''); 9261 $('.item_filter--selectbox .selectboxit').css('background', 'white') 9262 display = true; 9263 } 9264 return false; 9265 }); 9266 9267 $(window).on('resize', function() { 9268 if($(this).width() > SCREEN_SM) 9269 { 9270 $('.item-filter--categories').show(); 9271 } 9272 else { 9273 $('.item-filter--categories').hide(); 9274 } 9275 }); 9276 }); 9277 9278 9279}(jQuery)); 9280 9281document.addEventListener("DOMContentLoaded", () => { 9282 document.querySelectorAll(".interactive-image").forEach((interactiveImage) => { 9283 const instanceID = interactiveImage.id; 9284 const modalWrapper = document.querySelector(`.modal-wrapper#${instanceID}`); 9285 const closeButton = modalWrapper.querySelector(".close-icon.icon-x"); 9286 const nextButton = modalWrapper.querySelector(".right-icon.icon-rightarrow"); 9287 const prevButton = modalWrapper.querySelector(".left-icon.icon-leftarrow"); 9288 const ul = interactiveImage.querySelector(".interactive-image-wrapper ul"); 9289 9290 let currentIndex = -1; 9291 let previouslyFocusedElement = null; 9292 const interactiveImages = document.querySelectorAll(".interactive-image"); 9293 9294 // Disable focus on background elements 9295 function disableBackgroundFocus() { 9296 interactiveImages.forEach((image) => { 9297 if (image !== modalWrapper) { 9298 image.setAttribute("inert", "true"); 9299 image.setAttribute("aria-hidden", "true"); 9300 } 9301 }); 9302 } 9303 9304 // Enable focus on background elements 9305 function enableBackgroundFocus() { 9306 interactiveImages.forEach((image) => { 9307 image.removeAttribute("inert"); 9308 image.setAttribute("aria-hidden", "false"); 9309 }); 9310 } 9311 9312 // Open Modal 9313 interactiveImage.querySelector(".interactive-image-wrapper").addEventListener("click", (event) => { 9314 if (event.target.tagName === "BUTTON") { 9315 previouslyFocusedElement = document.activeElement; 9316 9317 const button = event.target; 9318 button.classList.add("is-disabled"); 9319 currentIndex = Array.from(ul.querySelectorAll("li")).indexOf(button.closest("li")); 9320 9321 updateModalContent(currentIndex); 9322 modalWrapper.classList.add("is-visible"); 9323 9324 disableBackgroundFocus(); 9325 trapFocus(); 9326 9327 document.addEventListener("keydown", handleKeydown); 9328 } 9329 }); 9330 9331 function updateModalContent(index) { 9332 const items = ul.querySelectorAll("li"); 9333 const item = items[index]; 9334 const button = item.querySelector("button"); 9335 9336 button.classList.add("is-disabled"); 9337 9338 const title = button.getAttribute("data-title"); 9339 const description = button.getAttribute("data-description"); 9340 const image = button.getAttribute("data-image"); 9341 const imageText = button.getAttribute("data-imagetext"); 9342 9343 const contentDiv = modalWrapper.querySelector(".content"); 9344 const titleWrapper = contentDiv.querySelector(".title-wrapper"); 9345 let pointContainer = titleWrapper.querySelector(".point-container"); 9346 9347 // If .point-container does NOT exist, do NOT add the button 9348 if (pointContainer) { 9349 // Clear and reuse point-container 9350 pointContainer.innerHTML = ""; 9351 9352 // Clone the button and add it to the point-container 9353 const clonedButton = button.cloneNode(true); 9354 pointContainer.appendChild(clonedButton); 9355 } 9356 9357 // Update title 9358 const existingTitle = titleWrapper.querySelector("h3"); 9359 if (existingTitle) existingTitle.remove(); 9360 if (title) { 9361 const h3 = document.createElement("h3"); 9362 h3.textContent = title; 9363 h3.tabIndex = 0; 9364 titleWrapper.appendChild(h3); 9365 } 9366 9367 // Update image 9368 const existingImageWrapper = contentDiv.querySelector(".image-wrapper"); 9369 if (existingImageWrapper) existingImageWrapper.remove(); 9370 if (image) { 9371 const imageWrapper = document.createElement("div"); 9372 imageWrapper.className = "image-wrapper"; 9373 9374 const img = document.createElement("img"); 9375 img.src = image; 9376 img.alt = imageText || title || "Image"; 9377 img.className = "description-image"; 9378 img.tabIndex = 0; 9379 9380 imageWrapper.appendChild(img); 9381 contentDiv.appendChild(imageWrapper); 9382 } 9383 9384 // Update description 9385 const existingDescription = contentDiv.querySelector(".description"); 9386 if (existingDescription) existingDescription.remove(); 9387 if (description) { 9388 const p = document.createElement("p"); 9389 p.className = "description"; 9390 p.innerHTML = description; 9391 p.tabIndex = 0; 9392 contentDiv.appendChild(p); 9393 } 9394 } 9395 9396 9397 // Handle Next Button Click 9398 function goToNext() { 9399 if (!nextButton) return; 9400 const contentDiv = modalWrapper.querySelector(".content"); 9401 9402 contentDiv.classList.add("transition-next"); 9403 setTimeout(() => { 9404 contentDiv.classList.remove("transition-next"); 9405 9406 const items = ul.querySelectorAll("li"); 9407 currentIndex = (currentIndex + 1) % items.length; 9408 updateModalContent(currentIndex); 9409 9410 contentDiv.classList.add("transition-in-from-left"); 9411 setTimeout(() => contentDiv.classList.remove("transition-in-from-left"), 200); 9412 }, 200); 9413 } 9414 9415 nextButton?.addEventListener("click", goToNext); 9416 9417 // Handle Previous Button Click 9418 function goToPrev() { 9419 if (!prevButton) return; 9420 const contentDiv = modalWrapper.querySelector(".content"); 9421 9422 contentDiv.classList.add("transition-prev"); 9423 setTimeout(() => { 9424 contentDiv.classList.remove("transition-prev"); 9425 9426 const items = ul.querySelectorAll("li"); 9427 currentIndex = (currentIndex - 1 + items.length) % items.length; 9428 updateModalContent(currentIndex); 9429 9430 contentDiv.classList.add("transition-in-from-right"); 9431 setTimeout(() => contentDiv.classList.remove("transition-in-from-right"), 200); 9432 }, 200); 9433 } 9434 9435 prevButton?.addEventListener("click", goToPrev); 9436
9437 // Close Modal 9438 function closeModal() { 9439 modalWrapper.classList.remove("is-visible"); 9440 currentIndex = -1; 9441 9442 document.removeEventListener("keydown", handleKeydown); 9443 enableBackgroundFocus(); 9444 9445 if (previouslyFocusedElement) { 9446 previouslyFocusedElement.focus(); 9447 } 9448 } 9449 9450 closeButton.addEventListener("click", closeModal); 9451 modalWrapper.addEventListener("click", (event) => { 9452 if (event.target === modalWrapper) { 9453 closeModal(); 9454 } 9455 }); 9456 9457 // Handle keyboard navigation with improved focus movement 9458 function handleKeydown(event) { 9459 if (!modalWrapper.classList.contains("is-visible")) return; 9460 9461 const focusableElements = modalWrapper.querySelectorAll( 9462 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 9463 ); 9464 const firstElement = focusableElements[0]; 9465 const lastElement = focusableElements[focusableElements.length - 1]; 9466 const activeIndex = Array.from(focusableElements).indexOf(document.activeElement); 9467 9468 switch (event.key) { 9469 case "Escape": 9470 closeModal(); 9471 break; 9472 case "Enter": 9473 if (document.activeElement) document.activeElement.click(); 9474 break; 9475 case "ArrowRight": 9476 if (nextButton) goToNext(); 9477 break; 9478 case "ArrowLeft": 9479 if (prevButton) goToPrev(); 9480 break; 9481 case "ArrowUp": 9482 event.preventDefault(); 9483 if (activeIndex > 0) { 9484 focusableElements[activeIndex - 1].focus(); 9485 } else { 9486 lastElement.focus(); // Wrap focus to last element 9487 } 9488 break; 9489 case "ArrowDown": 9490 event.preventDefault(); 9491 if (activeIndex < focusableElements.length - 1) { 9492 focusableElements[activeIndex + 1].focus(); 9493 } else { 9494 firstElement.focus(); // Wrap focus to first element 9495 } 9496 break; 9497 case "Tab": 9498 if (event.shiftKey && document.activeElement === firstElement) { 9499 event.preventDefault(); 9500 lastElement.focus(); 9501 } else if (!event.shiftKey && document.activeElement === lastElement) { 9502 event.preventDefault(); 9503 firstElement.focus(); 9504 } 9505 break; 9506 } 9507 } 9508 9509 function trapFocus() { 9510 const focusableElements = modalWrapper.querySelectorAll( 9511 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 9512 ); 9513 if (focusableElements.length > 0) { 9514 focusableElements[0].focus(); 9515 } 9516 } 9517 // Open Modal - Save previous focus and trap focus inside modal 9518 interactiveImage 9519 .querySelector('.interactive-image-wrapper') 9520 .addEventListener('click', (event) => { 9521 if (event.target.tagName === 'BUTTON') { 9522 const button = event.target; 9523 9524 // Save previously focused element 9525 previouslyFocusedElement = document.activeElement; 9526 9527 button.classList.add('is-disabled'); 9528 currentIndex = Array.from(ul.querySelectorAll('li')).indexOf( 9529 button.closest('li') 9530 ); 9531 9532 updateModalContent(currentIndex); 9533 modalWrapper.classList.add('is-visible'); 9534 9535 // Trap focus inside modal 9536 trapFocus(); 9537 9538 document.addEventListener('keydown', handleKeydown); 9539 } 9540 }); 9541 }); 9542 9543}); 9544 9545$(document).ready(function () { 9546 function isAuthorMode() { 9547 return document.cookie.split("; ").some(cookie => cookie.startsWith("wcmmode=edit")); 9548 } 9549 9550 if (isAuthorMode()) { 9551 const $container = $(".interactive-container .interactive-image-wrapper"); 9552 console.log($container); 9553 9554 if ($container.length) { 9555 $container.on("click", function (event) { 9556 console.log("clicked"); 9557 9558 const rect = this.getBoundingClientRect(); 9559 const offsetX = event.clientX - rect.left; 9560 const offsetY = event.clientY - rect.top; 9561 9562 const percentX = ((offsetX / rect.width) * 100).toFixed(2); 9563 const percentY = ((offsetY / rect.height) * 100).toFixed(2); 9564 9565 console.log(`top: ${offsetY}px; left: ${offsetX}px`); 9566 console.log(`top: ${percentY}%; left: ${percentX}%`); 9567 }); 9568 } 9569 } 9570}); 9571 9572 9573$(function(){ 9574 var $galleryContainer = $(".gallery"); 9575 $galleryContainer.each(function(){ 9576 var galleryTemplate = $(this).data("galleryTemplate"); 9577 var autoPlay = $(this).data("galleryAutoplay"); 9578 var id = $(this).attr("id"); 9579 var galleryEle = '#'+id+' .gallery-top'; 9580 var thumbnailEle ='#'+id+' .gallery-thumbs'; 9581 var paginationEle = '#'+id+ ' .swiper-pagination'; 9582 var countSlides = $(galleryEle + ' .swiper-slide').length; 9583 var default_autoplay = 8000; 9584 var params = { 9585 keyboardControl: true, 9586 navigation: { 9587 nextEl: '.gallery-button-next', 9588 prevEl: '.gallery-button-prev', 9589 }, 9590 loop: (countSlides >= 6), 9591 loopedSlides: 7, 9592 preloadImages: false, 9593 autoHeight: true, 9594 lazyLoading: true, 9595 lazyLoadingInPrevNext: true, 9596 lazyLoadingOnTransitionStart: true, 9597 lazyLoadingInPrevNextAmount: 2, 9598 pagination:{ 9599 el: paginationEle, 9600 type: 'fraction' 9601 }, 9602 a11y: true 9603 }; 9604 if(autoPlay){ 9605 params.autoplay={ 9606 delay: default_autoplay, 9607 }; 9608 } 9609 9610 var thumbnailConfig={ 9611 spaceBetween: 0, 9612 centeredSlides: true, 9613 slidesPerView: (countSlides <= 6) ? 5 : 6, 9614 touchRatio: 0.2, 9615 slideToClickedSlide: true, 9616 loop: (countSlides >= 6), // loop is false when fewer slides than slidesPerView 9617 loopedSlides: 7 9618 }; 9619 9620 // setup the gallery 9621 var galleryTop = new Swiper (galleryEle,params); 9622 if(galleryTemplate!=="nothumbnail"){ 9623 var galleryThumbs = new Swiper(thumbnailEle, thumbnailConfig); 9624 galleryTop.thumbs = galleryThumbs; 9625 galleryTop.controller.control =galleryThumbs; 9626 galleryThumbs.controller.control = galleryTop; 9627 9628 9629 } 9630 }); 9631 9632 var parseId = function(url) { 9633 var re = /(embed|video)\/(.+)?\??/; 9634 return re.exec(url)[2]; 9635 }; 9636 9637 //video modal 9638 $('.video--link').magnificPopup({ 9639 type: 'iframe', 9640 mainClass: 'mfp-zoom-in', 9641 removalDelay: 500, /*needed for out animation*/ 9642 midClick: true, 9643 iframe: { 9644 patterns: { 9645 youtube: { 9646 index: 'youtube.com/', 9647 id: parseId, 9648 src: 'https://www.youtube.com/embed/%id%?autoplay=1&rel=0' 9649 }, 9650 vimeo: { 9651 index: 'vimeo.com/', 9652 id: parseId, 9653 src: '//player.vimeo.com/video/%id%?autoplay=1' 9654 } 9655 } 9656 } 9657 }); 9658 9659 //brightcove video modal 9660 $('.video__brightcove--link').magnificPopup({ 9661 type:'inline', 9662 mainClass: 'mfp-zoom-in', 9663 removalDelay: 500, /*needed for out animation*/ 9664 midClick: true, 9665 callbacks: { 9666 close: function() { 9667 var allPlayers = videojs.getPlayers();
9668 for(var p in allPlayers){ 9669 if(typeof p !=="undefined"){ 9670 allPlayers[p].pause(); 9671 } 9672 } 9673 } 9674 } 9675 }); 9676 9677 9678}); 9679 9680 9681$(function() { 9682 // initiate polyfill 9683 function resizeHeroBannerOnMobile() { 9684 if ($(window).outerWidth() <= 992) { 9685 var contentHeight = $('.wrap-hero-content').height(); 9686 $('.hero-banner--on-image .wrap-hero-content').parent().find('.hero-banner__img--mobile .hero-banner__img--container').height(contentHeight) 9687 } 9688 } 9689 9690 $(document).ready(function() { 9691 if (document.querySelector('.hero-banner__img picture img')) { 9692 var objectFit = new ObjectFitPolyfill('.hero-banner__img picture img'); 9693 $('.hero-banner__img picture img').css('opacity', '1'); 9694 $('.hero-banner__img--mobile picture img').css('width', '100%'); 9695 $('.hero-banner__img--mobile picture img').css('object-fit', 'cover'); 9696 9697 } 9698 resizeHeroBannerOnMobile(); 9699 $(window).on('resize', function() { 9700 resizeHeroBannerOnMobile(); 9701 }); 9702 }); 9703}); 9704 9705$(function () { 9706 var mobileNavBreakpoint = 991; 9707 9708 function updateHeaderNavOffset() { 9709 var $header = $('.header--version-2.site-header').first(); 9710 var headerHeight = Math.ceil($header.outerHeight() || 48); 9711 9712 document.documentElement.style.setProperty( 9713 '--header-mobile-offset', 9714 headerHeight + 'px' 9715 ); 9716 } 9717 9718 function updateNavContainer() { 9719 updateHeaderNavOffset(); 9720 9721 $('.header--version-2 .header__nav').each(function () { 9722 var $nav = $(this); 9723 var $navContainer = $nav.children('.header__nav-container'); 9724 var $buttonContainer = $nav.children('.header__nav-button-container'); 9725 var $mainNav = $nav.children('.header__main-nav'); 9726 var $auxNav = $nav.children('.header__aux-nav'); 9727 9728 if (!$mainNav.length && $navContainer.length) { 9729 $mainNav = $navContainer.children('.header__main-nav'); 9730 } 9731 9732 if (!$auxNav.length && $navContainer.length) { 9733 $auxNav = $navContainer.children('.header__aux-nav'); 9734 } 9735 9736 function restoreAuxButton() { 9737 if ($buttonContainer.length) { 9738 $auxNav.find('.aux-nav__list').first().append( 9739 $buttonContainer.children('.aux-nav__item--button') 9740 ); 9741 $buttonContainer.remove(); 9742 } 9743 } 9744 9745 if (!$mainNav.length || !$auxNav.length) { 9746 restoreAuxButton(); 9747 if ($navContainer.length) { 9748 $navContainer.children('.header__main-nav, .header__aux-nav').insertBefore( 9749 $navContainer 9750 ); 9751 $navContainer.remove(); 9752 } 9753 return; 9754 } 9755 9756 if (window.innerWidth <= mobileNavBreakpoint) { 9757 if (!$navContainer.length) {
9758 $navContainer = $('<div class="header__nav-container"></div>'); 9759 $mainNav.before($navContainer); 9760 $navContainer.append($mainNav, $auxNav); 9761 } 9762 if (!$buttonContainer.length) { 9763 $buttonContainer = $('<div class="header__nav-button-container"></div>'); 9764 $navContainer.after($buttonContainer); 9765 } 9766 $buttonContainer.append($auxNav.find('.aux-nav__item--button')); 9767 } else if ($navContainer.length) { 9768 restoreAuxButton(); 9769 $navContainer.children('.header__main-nav, .header__aux-nav').insertBefore( 9770 $navContainer 9771 ); 9772 $navContainer.remove(); 9773 } 9774 }); 9775 } 9776 9777 updateNavContainer(); 9778 $(window).on('resize', function () { 9779 updateNavContainer(); 9780 updateHeaderNavOffset(); 9781 }); 9782 9783 var parser = new UAParser(); 9784 /******************** 9785 Main Navigation 9786 *********************/ 9787 var desktop = true; 9788 var parser = new UAParser(); 9789 if ( 9790 parser.getDevice().type == 'mobile' || 9791 parser.getDevice().type == 'tablet' 9792 ) { 9793 desktop = false; 9794 } 9795 9796 //nav 9797 var $trigger = $('.header--version-2 .header__menu-trigger'); 9798 var $nav = $('.header--version-2 .header__nav'); 9799 9800 //sub nav 9801 var $triggerSubNav = $('.level-one__item .main-nav__link .main-nav__icon'); 9802 var $subNav = $('.header--version-2 .sub-nav__wrapper'); 9803 9804 //search 9805 var $triggerSearch = $('.header--version-2 .header__search-trigger'); 9806 var $searchPopup = $('.header__search--global.header__search-popup'); 9807 var $closePopUp = $('.header__search--global .header__search-trigger--close'); 9808 9809 //Blackout 9810 var $headerBlackout = $('.header-blackout'); 9811 9812 var $soloPrimaryLinks = $( 9813 '.main-nav__item.level-one__item:not(.main-nav__item--has-children' 9814 ); 9815 9816 function setHeaderNavState(isOpen) { 9817 $('.site-header').css('position', 'fixed'); 9818 $('.site-header').css('top', 0); 9819 $('.site-header').css('width', '100%'); 9820 $('.site-header').css('height', 'auto'); 9821 updateHeaderNavOffset(); 9822 } 9823 9824 $trigger.on('click', function (e) { 9825 e.preventDefault(); 9826 $this = $(this); 9827 $trigger.toggleClass('is-active'); 9828 $nav.toggleClass('is-active'); 9829 9830 $('html, body').toggleClass('nav-open', $trigger.hasClass('is-active')); 9831 9832 $subNav.slideUp(); 9833 $headerBlackout.toggleClass('is-active'); 9834 //hide search popup 9835 $searchPopup.removeClass('is-active'); 9836 9837 if ($trigger.hasClass('is-active')) { 9838 setHeaderNavState(true); 9839 } else { 9840 setHeaderNavState(false); 9841 cleanNav(); 9842 } 9843 }); 9844 9845 $('.level-one__icon').on('click', function (e) { 9846 e.preventDefault(); 9847 $this = $(this); 9848 if ($this.hasClass('is-active')) { 9849 returnOne($this); 9850 } else { 9851 $triggerSubNav.removeClass('is-active'); 9852 $subNav.hide(); 9853 $this.addClass('is-active').parent().next().show(); 9854 var $otherTriggerSubNav = $( 9855 '.level-one__icon.main-nav__icon:not(".is-active")' 9856 ); 9857 $otherTriggerSubNav.parent().hide(); 9858 $this.parent().next().addClass('active'); 9859 $this.parent().addClass('active-link'); 9860 $('.aux-nav__item:not(.aux-nav__item--button)').hide(); 9861 $soloPrimaryLinks.hide(); 9862 } 9863 }); 9864 9865 $('.level-two__icon').on('click', function (e) { 9866 e.preventDefault(); 9867 $this = $(this); 9868 if ($this.hasClass('is-active')) { 9869 returnTwo($this); 9870 } else { 9871 $this.addClass('is-active'); 9872 $this.parent().parent().addClass('level-two--active'); 9873 $('.level-two--item:not(.level-two--active)').hide(); 9874 var $otherTriggerSubNav = $( 9875 '.level-one__item .main-nav__link .main-nav__icon:not(".is-active")' 9876 ); 9877 $otherTriggerSubNav.parent().hide(); 9878 $('.active-link').hide(); 9879 $this.parent().addClass('active-link-two'); 9880 $this.parent().next().addClass('active'); 9881 $this.parent().next().addClass('active-two'); 9882 $('.header__nav').addClass('grey-one'); 9883 $this.parent().addClass('active-link-style'); 9884 } 9885 }); 9886 9887 $('.level-three__icon').on('click', function (e) { 9888 e.preventDefault(); 9889 $this = $(this); 9890 9891 if ($this.hasClass('is-active')) { 9892 returnThree($this); 9893 } else { 9894 $this.parent().addClass('active-link-style'); 9895 $this.addClass('is-active'); 9896 $this.parent().parent().addClass('level-three--active'); 9897 $('.level-three--item:not(.level-three--active)').hide(); 9898 var $otherTriggerSubNav = $( 9899 '.level-three__icon .main-nav__icon:not(".is-active")' 9900 ); 9901 $otherTriggerSubNav.parent().hide();
9902 $('.level-two--active .level-two-link').hide(); 9903 $this.parent().next().addClass('active'); 9904 $('.header__nav').addClass('grey-two'); 9905 $('.header__nav').removeClass('grey-one'); 9906 } 9907 }); 9908 $('.dropdownv2 p.aux-nav__link .aux-nav__text').on('click', function (e) { 9909 if (window.innerWidth > 991 || !desktop) { 9910 return; 9911 } 9912 e.preventDefault(); 9913 $this = $(this).parent().find('.aux-nav-js'); 9914 9915 if ($this.hasClass('is-active')) { 9916 returnAux($this); 9917 } else { 9918 $('.header__main-nav').hide(); 9919 $this.addClass('is-active'); 9920 $(this).parent().parent().addClass('aux-active'); 9921 $(this).parent().next().show(); 9922 $('.aux-nav__item:not(.aux-active, .aux-nav__item--button)').hide(); 9923 } 9924 }); 9925 9926 $('.aux-nav-js').on('click', function (e) { 9927 if (window.innerWidth > 991 || !desktop) { 9928 return; 9929 } 9930 e.preventDefault(); 9931 $this = $(this); 9932 9933 if ($this.hasClass('is-active')) { 9934 returnAux($this); 9935 } else { 9936 $('.header__main-nav').hide(); 9937 $this.addClass('is-active'); 9938 $(this).parent().parent().addClass('aux-active'); 9939 $(this).parent().next().show(); 9940 $('.aux-nav__item:not(.aux-active, .aux-nav__item--button)').hide(); 9941 } 9942 }); 9943 9944 $triggerSearch.on('click', function (e) { 9945 e.preventDefault(); 9946 9947 $this = $(this); 9948 $this.toggleClass('is-active'); 9949 $searchPopup.toggleClass('is-active'); 9950 }); 9951 9952 $closePopUp.on('click', function (e) { 9953 e.preventDefault(); 9954 $triggerSearch.removeClass('is-active'); 9955 $searchPopup.removeClass('is-active'); 9956 }); 9957 9958 function cleanNav() { 9959 var aux = $('.aux-nav-js'); 9960 var three = $('.level-three__icon'); 9961 var two = $('.level-two__icon'); 9962 var one = $('.level-one__icon'); 9963 9964 if (aux.hasClass('is-active')) { 9965 returnAux(aux); 9966 return; 9967 } 9968 returnThree(three); 9969 returnTwo(two); 9970 returnOne(one); 9971 } 9972 9973 function returnAux(aux) { 9974 $('.header__main-nav').show(); 9975 aux.removeClass('is-active'); 9976 $(aux).parent().parent().removeClass('aux-active'); 9977 $('.aux-nav__item').show(); 9978 $('.aux-nav__sub-nav').hide(); 9979 } 9980 9981 function returnThree(three) { 9982 $('.level-two--active .level-two-link').show(); 9983 three.removeClass('is-active'); 9984 $('.level-three--item').show(); 9985 $('.level-three--active').removeClass('level-three--active'); 9986 $('.level-four').removeClass('active'); 9987 $('.header__nav').addClass('grey-one'); 9988 $('.header__nav').removeClass('grey-two'); 9989 three.parent().removeClass('active-link-style'); 9990 } 9991 function returnTwo(two) { 9992 $('.active-link').show(); 9993 two.removeClass('is-active'); 9994 $('.level-two--item').show(); 9995 $('.level-two--active').removeClass('level-two--active'); 9996 $('.level-three').removeClass('active'); 9997 $('.header__nav').removeClass('grey-one'); 9998 two.parent().removeClass('active-link-style'); 9999 } 10000 function returnOne(one) { 10001 $triggerSubNav.parent().show(); 10002 $triggerSubNav.removeClass('is-active'); 10003 $subNav.hide(); 10004 $triggerSubNav.parent().next().removeClass('active'); 10005 $('.aux-nav__item').show(); 10006 one.parent().removeClass('active-link'); 10007 $soloPrimaryLinks.show(); 10008 } 10009}); 10010 10011var parser = new UAParser(); 10012if ( 10013 parser.getDevice().type == 'mobile' || 10014 parser.getDevice().type == 'tablet' 10015) { 10016 setupNav(); 10017} 10018 10019function setupNav() { 10020 $('.dropdownv2 p.aux-nav__link .aux-nav__text').on('click', function (e) { 10021 e.preventDefault(); 10022 $this = $(this).parent().find('.aux-nav-js'); 10023 10024 if ($this.hasClass('is-active')) { 10025 returnAux($this); 10026 } else { 10027 $('.header__main-nav').hide(); 10028 $this.addClass('is-active'); 10029 $(this).parent().parent().addClass('aux-active'); 10030 $(this).parent().next().show(); 10031 $('.aux-nav__item:not(.aux-active, .aux-nav__item--button)').hide(); 10032 } 10033 }); 10034 10035 $('.aux-nav-js').on('click', function (e) { 10036 10037 e.preventDefault(); 10038 $this = $(this); 10039 10040 if ($this.hasClass('is-active')) { 10041 returnAux($this); 10042 } else { 10043 $('.header__main-nav').hide(); 10044 $this.addClass('is-active'); 10045 $(this).parent().parent().addClass('aux-active'); 10046 $(this).parent().next().show(); 10047 $('.aux-nav__item:not(.aux-active, .aux-nav__item--button)').hide(); 10048 } 10049 }); 10050 // Select the element with the class "headerv2" 10051 var headerElement = document.querySelector('.headerv2'); 10052 var headerTrigger = document.querySelector( 10053 '.header__menu-trigger.header__menu-trigger--hamburger' 10054 ); 10055 var searchTrigger = document.querySelector( 10056 '.header__menu-trigger--search.header__menu-trigger' 10057 ); 10058 // Check if the element is found 10059 if (headerElement) { 10060 // Add a new class to the element 10061 headerElement.classList.add('enable-mobile'); 10062 headerElement.parentElement.parentElement.parentElement.parentElement.parentElement.classList.add( 10063 'enable-mobile' 10064 ); 10065
10066 if (headerTrigger) { 10067 headerTrigger.style.position = 'absolute'; 10068 headerTrigger.style.left = '5px'; 10069 } 10070 if (searchTrigger) { 10071 searchTrigger.style.position = 'absolute'; 10072 searchTrigger.style.right = '5px'; 10073 } 10074 } 10075} 10076 10077function returnAux(aux) { 10078 $('.header__main-nav').show(); 10079 aux.removeClass('is-active'); 10080 $(aux).parent().parent().removeClass('aux-active'); 10081 $('.aux-nav__item').show(); 10082 $('.aux-nav__sub-nav').hide(); 10083} 10084 10085var parser = new UAParser(); 10086$(window).on('load', function () { 10087 function handleResizeEvent() { 10088 if ( 10089 parser.getDevice().type == 'mobile' || 10090 parser.getDevice().type == 'tablet' 10091 ) { 10092 return; 10093 } 10094 const windowWidth = window.innerWidth; 10095 const wrapper = document.querySelector('.header--version-2 .sub-nav__wrapper'); 10096 let widthAvailable = windowWidth; 10097 10098 // Adjust widthAvailable by subtracting the description width, if available 10099 if (wrapper) { 10100 const description = wrapper.querySelector('.header--version-2 .sub-nav__description'); 10101 if (description) { 10102 widthAvailable -= Math.floor( 10103 parseFloat(getComputedStyle(description).width) 10104 ); 10105 } 10106 } 10107 10108 // Query level elements once for efficiency 10109 const levelElements = document.querySelectorAll( 10110 '.header--version-2 .sub-nav__list.level-two, .sub-nav__list.level-three, .sub-nav__list.level-four' 10111 ); 10112 10113 // Only apply custom widths when window width is between 992 and 1300 10114 const customWidthRequired = windowWidth > 991 && windowWidth < 1400; 10115 10116 levelElements.forEach((levelElement) => { 10117 levelElement.style.width = customWidthRequired 10118 ? `${widthAvailable}px` 10119 : ''; 10120 if (levelElement.classList.contains('level-four')) { 10121 const subNavItems = levelElement.querySelectorAll('.sub-nav__item'); 10122 subNavItems.forEach((item) => { 10123 item.style.width = customWidthRequired 10124 ? `${widthAvailable * 0.20}px` 10125 : ''; 10126 item.style.minWidth = customWidthRequired 10127 ? `${widthAvailable * 0.20}px` 10128 : ''; 10129 10130 }); 10131 } else { 10132 const subNavItems = levelElement.querySelectorAll('.sub-nav__item'); 10133 subNavItems.forEach((item) => { 10134 item.style.width = customWidthRequired 10135 ? `${widthAvailable * 0.25}px` 10136 : ''; 10137 }); 10138 } 10139 }); 10140 } 10141 10142 // Initial call and binding to resize 10143 handleResizeEvent(); 10144 $(window).on('resize', handleResizeEvent); 10145}); 10146 10147window.addEventListener('resize', () => { 10148 if (window.innerWidth > 991) { 10149 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('position', 'fixed'); 10150 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('width', '100%'); 10151 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('height', 'auto'); 10152 } else { 10153 if ($('.experiencefragment:not(.enable-mobile) .header--version-2 .header-blackout').hasClass('is-active')) { 10154 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('position','relative'); 10155 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('height', '100%'); 10156 } 10157 } 10158}); 10159 10160 10161window.addEventListener('load', () => { 10162 if (window.innerWidth > 991) { 10163 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('position', 'fixed'); 10164 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('width', '100%'); 10165 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('height', 'auto'); 10166 } else { 10167 if ($('.experiencefragment:not(.enable-mobile) .header--version-2 .header-blackout').hasClass('is-active')) { 10168 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('position','relative'); 10169 $('.experiencefragment:not(.enable-mobile) .global-header.site-header.header--version-2').css('height', '100%'); 10170 } 10171 } 10172}); 10173 10174$(function () {
10175 var mobileNavBreakpoint = 991; 10176 10177 function updateHeaderNavOffset() { 10178 var $header = $('.global-header.site-header:not(.header--version-2)').first(); 10179 var headerHeight = Math.ceil($header.outerHeight() || 48); 10180 10181 document.documentElement.style.setProperty('--header-mobile-offset', headerHeight + 'px'); 10182 } 10183 10184 function updateNavContainer() { 10185 updateHeaderNavOffset(); 10186 10187 $('.global-header:not(.header--version-2) .header__nav').each(function () { 10188 var $nav = $(this); 10189 var $navContainer = $nav.children('.header__nav-container'); 10190 var $buttonContainer = $nav.children('.header__nav-button-container'); 10191 var $mainNav = $nav.children('.header__main-nav'); 10192 var $auxNav = $nav.children('.header__aux-nav'); 10193 10194 if (!$mainNav.length && $navContainer.length) { 10195 $mainNav = $navContainer.children('.header__main-nav'); 10196 } 10197 10198 if (!$auxNav.length && $navContainer.length) { 10199 $auxNav = $navContainer.children('.header__aux-nav'); 10200 } 10201 10202 function restoreAuxButton() { 10203 if ($buttonContainer.length) { 10204 $auxNav.find('.aux-nav__list').first().append($buttonContainer.children('.aux-nav__item--button')); 10205 $buttonContainer.remove(); 10206 } 10207 } 10208 10209 if (!$mainNav.length || !$auxNav.length) { 10210 restoreAuxButton(); 10211 if ($navContainer.length) { 10212 $navContainer.children('.header__main-nav, .header__aux-nav').insertBefore($navContainer); 10213 $navContainer.remove(); 10214 } 10215 return; 10216 } 10217 10218 if (window.innerWidth <= mobileNavBreakpoint) { 10219 if (!$navContainer.length) {
10220 $navContainer = $('<div class="header__nav-container"></div>'); 10221 $mainNav.before($navContainer); 10222 $navContainer.append($mainNav, $auxNav); 10223 } 10224 if (!$buttonContainer.length) { 10225 $buttonContainer = $('<div class="header__nav-button-container"></div>'); 10226 $navContainer.after($buttonContainer); 10227 } 10228 $buttonContainer.append($auxNav.find('.aux-nav__item--button')); 10229 } else if ($navContainer.length) { 10230 restoreAuxButton(); 10231 $navContainer.children('.header__main-nav, .header__aux-nav').insertBefore($navContainer); 10232 $navContainer.remove(); 10233 } 10234 }); 10235 } 10236 10237 updateNavContainer(); 10238 $(window).on('resize', function () { 10239 updateNavContainer(); 10240 updateHeaderNavOffset(); 10241 }); 10242 10243 /******************** 10244 Main Navigation 10245 *********************/ 10246 10247 //nav 10248 var $trigger = $('.global-header:not(.header--version-2) .header__menu-trigger'); 10249 var $nav = $('.global-header:not(.header--version-2) .header__nav'); 10250 10251 //sub nav 10252 var $triggerSubNav = $('.global-header:not(.header--version-2) .main-nav__item--has-children .main-nav__icon') 10253 var $subNav = $('.global-header:not(.header--version-2) .sub-nav__wrapper'); 10254 10255 //search 10256 var $triggerSearch = $('.global-header:not(.header--version-2) .header__search-trigger'); 10257 var $searchPopup = $('.header__search--global.header__search-popup'); 10258 var $closePopUp = $('.header__search--global .header__search-trigger--close'); 10259 10260 //Blackout 10261 var $headerBlackout = $('.header-blackout'); 10262 10263 function setHeaderNavState(isOpen) { 10264 $('.site-header').css("position", 'fixed'); 10265 $('.site-header').css("top", 0); 10266 $('.site-header').css("width", '100%'); 10267 $('.site-header').css("height", 'auto'); 10268 updateHeaderNavOffset(); 10269 } 10270 10271 $trigger.on("click", function (e) { 10272 e.preventDefault(); 10273 $this = $(this); 10274 $trigger.toggleClass('is-active'); 10275 $nav.toggleClass('is-active'); 10276 10277 $('html, body').toggleClass('nav-open', $trigger.hasClass('is-active')); 10278 10279 $subNav.slideUp(); 10280 $headerBlackout.toggleClass('is-active'); 10281 //hide search popup 10282 $searchPopup.removeClass('is-active'); 10283 10284 if($trigger.hasClass('is-active')) 10285 { 10286 setHeaderNavState(true); 10287 } 10288 else { 10289 setHeaderNavState(false); 10290 } 10291 }); 10292 10293 $triggerSubNav.on("click", function (e) { 10294 e.preventDefault(); 10295 $this = $(this); 10296 10297 if ($this.hasClass('is-active')) { 10298 $triggerSubNav.removeClass('is-active'); 10299 $subNav.slideUp(); 10300 } else { 10301 $triggerSubNav.removeClass('is-active'); 10302 $subNav.slideUp(); 10303 $this.addClass('is-active').parent().next().slideDown(); 10304 } 10305 }) 10306 10307 $triggerSearch.on("click", function (e) { 10308 e.preventDefault(); 10309
10310 $this = $(this); 10311 $this.toggleClass('is-active'); 10312 $searchPopup.toggleClass('is-active'); 10313 }); 10314 10315 $closePopUp.on("click", function (e) { 10316 e.preventDefault(); 10317 $triggerSearch.removeClass('is-active'); 10318 $searchPopup.removeClass('is-active'); 10319 }); 10320 10321 $headerBlackout.on("click", function (e) { 10322 e.preventDefault(); 10323 $trigger.removeClass('is-active'); 10324 $nav.removeClass('is-active'); 10325 $('html, body').removeClass('nav-open'); 10326 $triggerSubNav.removeClass('is-active') 10327 $subNav.slideUp(); 10328 }); 10329 10330 var experiencefragment = $('.experiencefragment'); 10331 function setHeader() { 10332 10333 if(experiencefragment.length > 0) 10334 { 10335 $(experiencefragment[0]).css("height", '0px'); 10336 } 10337 $('.global-header.site-header:not(.header--version-2)').css("position", 'fixed'); 10338 $('.global-header.site-header:not(.header--version-2)').css("width", '100%'); 10339 $('.global-header.site-header:not(.header--version-2)').css("height", 'auto'); 10340 $('.experiencefragment:first-of-type + .responsivegrid').css("margin-top", $('.site-header').height()); 10341 } 10342 10343 $(document).ready(function(){ 10344 setHeader(); 10345 }); 10346 10347 $(window).on('resize', function() { 10348 setHeader(); 10349 }); 10350 10351 setHeader(); 10352 10353 10354 10355 10356}); 10357 10358$(function () { 10359 /******************** 10360 AUX Navigation 10361 *********************/ 10362 10363 //sub nav 10364 var $triggerSubNav = $('.global-header:not(.header--version-2) .aux-nav__item--dropdown .aux-nav__icon') 10365 var $subNav = $('.global-header:not(.header--version-2) .aux-nav__sub-nav'); 10366 10367 $triggerSubNav.on("click", function (e) { 10368 e.preventDefault(); 10369 $this = $(this); 10370 10371 if ($this.hasClass('is-active')) { 10372 $triggerSubNav.removeClass('is-active'); 10373 $subNav.slideUp(); 10374 } else { 10375 $triggerSubNav.removeClass('is-active'); 10376 $subNav.slideUp(); 10377 $this.addClass('is-active').parent().next().slideDown(); 10378 } 10379 }) 10380 10381 10382 10383 10384}); 10385 10386$('input[name="q"]').change(function() { 10387 $('input[name="query"]').val($(this).val()); 10388}); 10389var mapList = []; 10390var mapById = {}; // Store maps by their ID for reliable lookup 10391var mapMarkersByCategory = {}; // Store markers organized by category for filtering 10392var mapClusterers = {}; // Store clusterers for each map 10393var openInfoWindows = {}; // Store the active info window for each map 10394 10395// Helper function to check if we're on mobile 10396function isMobileDevice() { 10397 return window.innerWidth <= 768; 10398} 10399 10400// Create and show mobile full-screen modal for infowindow content 10401function showMobileInfoModal(content) { 10402 // Remove any existing modal 10403 closeMobileInfoModal(); 10404 10405 var modal = document.createElement('div'); 10406 modal.className = 'environmental-mobile-modal'; 10407 modal.innerHTML = 10408 '<div class="environmental-mobile-modal__overlay"></div>' + 10409 '<div class="environmental-mobile-modal__container">' + 10410 '<button class="environmental-mobile-modal__close" aria-label="Close">' + 10411 '<span aria-hidden="true">×</span>' + 10412 '</button>' + 10413 '<div class="environmental-mobile-modal__content">' + content + '</div>' + 10414 '</div>'; 10415 10416 document.body.appendChild(modal); 10417 document.body.style.overflow = 'hidden'; 10418 10419 // Add close handlers 10420 modal.querySelector('.environmental-mobile-modal__close').addEventListener('click', closeMobileInfoModal); 10421 modal.querySelector('.environmental-mobile-modal__overlay').addEventListener('click', closeMobileInfoModal); 10422 10423 // Re-attach video trigger handlers for the modal content 10424 attachVideoTriggerHandlers(); 10425} 10426 10427// Close mobile info modal 10428function closeMobileInfoModal() { 10429 var existingModal = document.querySelector('.environmental-mobile-modal'); 10430 if (existingModal) { 10431 existingModal.remove(); 10432 document.body.style.overflow = ''; 10433 } 10434} 10435 10436function unescapeToHtml(value) { 10437 if (!value || typeof value !== 'string') return ''; 10438 10439 return value 10440 .replace(/\\\\/g, '\\') // double backslashes -> single backslash 10441 .replace(/\\"/g, '"') // escaped double quote -> actual " 10442 .replace(/\\'/g, "'") // escaped single quote -> actual '
10443 .replace(/\\n/g, '<br>') // escaped newline -> <br> 10444 .replace(/\\t/g, ' ') // escaped tab -> em space 10445} 10446 10447addEventListener('DOMContentLoaded', () => { 10448 $('.google-map-component').each(function (index, mapElement) { 10449 var longitude = $(mapElement).data('central-lng'); 10450 var latitude = $(mapElement).data('central-lat'); 10451 var zoom = $(mapElement).data('zoom'); 10452 var type = $(mapElement).data('map-type'); 10453 var hybridTilt = $(mapElement).data('hybrid-tilt'); 10454 var satelliteTilt = $(mapElement).data('satellite-tilt'); 10455 var formatting = $(mapElement).data('formatting'); 10456 var legendPosition = $(mapElement).data('legend-position'); 10457 var setTilt = 0; 10458 if (type == 'satellite' && satelliteTilt) { 10459 setTilt = 45; 10460 } 10461 if (type == 'hybrid' && hybridTilt) { 10462 setTilt = 45; 10463 } 10464 10465 // Check if environmental effort format is selected 10466 if (formatting === 'environmentalEffort') { 10467 // Request user's location for cluster maps 10468 if (navigator.geolocation) { 10469 navigator.geolocation.getCurrentPosition( 10470 function(position) { 10471 // Success: Use user's location 10472 var userLat = position.coords.latitude; 10473 var userLng = position.coords.longitude; 10474 10475 var map = new google.maps.Map(mapElement, { 10476 center: { lat: userLat, lng: userLng }, 10477 zoom: parseInt(zoom), 10478 mapTypeId: type, 10479 tilt: setTilt, 10480 mapId: $(mapElement).attr('id') 10481 }); 10482 mapList.push(map); 10483 mapById[$(mapElement).attr('id')] = map; // Store map by ID 10484 10485 initEnvironmentalEffortMap(index, mapElement, map, legendPosition); 10486 }, 10487 function(error) { 10488 // Error or denied: Use configured location 10489 10490 var map = new google.maps.Map(mapElement, { 10491 center: { lat: parseFloat(latitude), lng: parseFloat(longitude) }, 10492 zoom: parseInt(zoom), 10493 mapTypeId: type, 10494 tilt: setTilt, 10495 mapId: $(mapElement).attr('id') 10496 }); 10497 mapList.push(map); 10498 mapById[$(mapElement).attr('id')] = map; // Store map by ID 10499 10500 initEnvironmentalEffortMap(index, mapElement, map, legendPosition); 10501 }, 10502 { 10503 timeout: 5000,
10504 enableHighAccuracy: false 10505 } 10506 ); 10507 } else { 10508 // Geolocation not supported: Use configured location 10509 console.warn('Geolocation not supported, using configured location'); 10510 10511 var map = new google.maps.Map(mapElement, { 10512 center: { lat: parseFloat(latitude), lng: parseFloat(longitude) }, 10513 zoom: parseInt(zoom), 10514 mapTypeId: type, 10515 tilt: setTilt, 10516 mapId: $(mapElement).attr('id') 10517 }); 10518 mapList.push(map); 10519 mapById[$(mapElement).attr('id')] = map; // Store map by ID 10520 10521 initEnvironmentalEffortMap(index, mapElement, map, legendPosition); 10522 } 10523 } else { 10524 // Non-environmental effort maps: Use configured location as before 10525 var map = new google.maps.Map(mapElement, { 10526 center: { lat: parseFloat(latitude), lng: parseFloat(longitude) }, 10527 zoom: parseInt(zoom), 10528 mapTypeId: type, 10529 tilt: setTilt, 10530 mapId: $(mapElement).attr('id') 10531 }); 10532 mapList.push(map); 10533 mapById[$(mapElement).attr('id')] = map; // Store map by ID 10534 10535 createMarkers(index); 10536 } 10537 }); 10538 10539}); 10540 10541/** 10542 * Initialize environmental effort map with markers and legend 10543 */ 10544function initEnvironmentalEffortMap(index, mapElement, map, legendPosition) { 10545 var endpoint = $(mapElement).data('environmental-effort-endpoint'); 10546 var cfPath = $(mapElement).data('environmental-effort-path'); 10547 var clusterOptions = { 10548 gridSize: parseInt($(mapElement).data('cluster-grid-size')) || 40, 10549 minPoints: parseInt($(mapElement).data('cluster-min-points')) || 2, 10550 maxZoom: parseInt($(mapElement).data('cluster-max-zoom')) || 14, 10551 color: $(mapElement).data('cluster-color') || '#0074D9' 10552 }; 10553 10554 // Parse category icon overrides 10555 var categoryIconOverrides = {}; 10556 try { 10557 var overridesJson = $(mapElement).data('category-icon-overrides'); 10558 10559 var overridesArray; 10560 if (typeof overridesJson === 'string') { 10561 // If it's a string, parse it 10562 overridesArray = JSON.parse(overridesJson); 10563 } else if (Array.isArray(overridesJson)) { 10564 // If jQuery already parsed it, use it directly 10565 overridesArray = overridesJson; 10566 } else { 10567 console.warn('Unexpected overrides data type'); 10568 overridesArray = []; 10569 } 10570 10571 overridesArray.forEach(function(override) { 10572 if (override.category && override.icon) { 10573 categoryIconOverrides[override.category.toLowerCase()] = { 10574 icon: override.icon, 10575 label: override.label || null // Store custom label if provided 10576 }; 10577 } 10578 }); 10579 } catch (e) { 10580 console.error('Failed to parse category icon overrides:', e, overridesJson); 10581 } 10582 10583 createEnvironmentalEffortMarkers(index, endpoint, cfPath, clusterOptions, categoryIconOverrides, mapElement, legendPosition); 10584} 10585 10586/** 10587 * Fetch environmental effort data from GraphQL endpoint and create clustered markers 10588 */ 10589function createEnvironmentalEffortMarkers(index, endpoint, cfPath, clusterOptions, categoryIconOverrides, mapElement, legendPosition) { 10590 if (!endpoint) { 10591 console.warn('Environmental effort endpoint not configured'); 10592 return; 10593 } 10594 10595 // Build the URL with path parameter if provided 10596 var requestUrl = endpoint; 10597 if (cfPath) { 10598 // Persisted-query variables are part of the path, 10599 requestUrl = endpoint + ';path=' + cfPath; 10600 } 10601 10602 console.log('Fetching environmental effort data from:', requestUrl); 10603 10604 fetch(requestUrl, { 10605 method: 'GET', 10606 headers: { 10607 'Content-Type': 'application/json' 10608 } 10609 }) 10610 .then(function(response) { 10611 if (!response.ok) { 10612 throw new Error('Network response was not ok'); 10613 } 10614 return response.json(); 10615 }) 10616 .then(function(responseData) { 10617 var items = responseData.data && responseData.data.environmentalEffortList && responseData.data.environmentalEffortList.items; 10618 10619 if (!items || items.length === 0) { 10620 console.warn('No environmental effort data found'); 10621 return; 10622 } 10623 10624 var markers = []; 10625 var map = mapList[index]; 10626 var mapId = $(mapElement).attr('id'); 10627 10628 // Initialize category tracking for this map 10629 mapMarkersByCategory[mapId] = {}; 10630 var categorySet = new Set(); 10631 var markerItems = prepareEnvironmentalEffortMarkerItems(items); 10632
10633 markerItems.forEach(function(markerItem) { 10634 var item = markerItem.item; 10635 var lat = markerItem.position.lat; 10636 var lng = markerItem.position.lng; 10637 10638 // Build info window content 10639 var infowindowContent = buildEnvironmentalEffortInfoWindow(item, categoryIconOverrides); 10640 10641 var infowindow = new google.maps.InfoWindow({ 10642 content: infowindowContent, 10643 maxWidth: 350 10644 }); 10645 10646 var customIcon = getCustomIconForCategory(item.category, categoryIconOverrides); 10647 10648 var markerOptions = { 10649 position: { lat: lat, lng: lng }, 10650 title: item.title || '', 10651 map: map 10652 }; 10653 10654 // Set custom icon if available 10655 // Use standard Marker class to support icon property 10656 var marker = new google.maps.Marker(markerOptions); 10657 10658 // Render the category artwork into a white circular marker. Rendering 10659 // the image first avoids a nested-SVG image that Google Maps displays 10660 // as an empty white circle. 10661 if (customIcon) { 10662 Promise.all([ 10663 createCircularMarkerIcon(customIcon), 10664 createCircularMarkerIcon(customIcon, null, null, true) 10665 ]) 10666 .then(function(markerIcons) { 10667 var normalIcon = { 10668 url: markerIcons[0], 10669 scaledSize: new google.maps.Size(48, 48), 10670 anchor: new google.maps.Point(24, 24) 10671 }; 10672 var hoverIcon = { 10673 url: markerIcons[1], 10674 // The larger canvas only provides room for the shadow; the 10675 // circular marker itself remains the same 48px diameter. 10676 scaledSize: new google.maps.Size(68, 68), 10677 anchor: new google.maps.Point(34, 34) 10678 }; 10679 10680 marker.setIcon(normalIcon); 10681 marker.addListener('mouseover', function() { 10682 marker.setIcon(hoverIcon); 10683 }); 10684 marker.addListener('mouseout', function() { 10685 marker.setIcon(normalIcon); 10686 }); 10687 marker.addListener('focus', function() { 10688 marker.setIcon(hoverIcon); 10689 }); 10690 marker.addListener('blur', function() { 10691 marker.setIcon(normalIcon); 10692 }); 10693 }) 10694 .catch(function() { 10695 // Keep the category image visible if an authored icon cannot be rendered. 10696 marker.setIcon({ 10697 url: customIcon, 10698 scaledSize: new google.maps.Size(40, 40), 10699 anchor: new google.maps.Point(20, 20) 10700 }); 10701 }); 10702 } 10703 10704 // Add click listener to show info window 10705 marker.addListener('click', function() { 10706 if (isMobileDevice()) { 10707 // On mobile, show custom full-screen modal 10708 showMobileInfoModal(infowindowContent); 10709 } else { 10710 // On desktop, use native Google Maps InfoWindow 10711 if (openInfoWindows[mapId] && openInfoWindows[mapId] !== infowindow) { 10712 openInfoWindows[mapId].close(); 10713 } 10714 infowindow.open({ 10715 map: map, 10716 anchor: marker, 10717 shouldFocus: false 10718 }); 10719 openInfoWindows[mapId] = infowindow; 10720 } 10721 }); 10722 10723 infowindow.addListener('closeclick', function() { 10724 if (openInfoWindows[mapId] === infowindow) { 10725 delete openInfoWindows[mapId]; 10726 } 10727 }); 10728 10729 markers.push(marker); 10730 10731 // Track markers by category for filtering 10732 if (item.category && item.category.length > 0) { 10733 item.category.forEach(function(cat) { 10734 var categoryKey = cat.toLowerCase(); 10735 var categoryLabel = cat.split(':').pop().replace(/-/g, ' ') || cat; 10736 10737 categorySet.add(JSON.stringify({ key: categoryKey, label: categoryLabel, original: cat })); 10738 10739 if (!mapMarkersByCategory[mapId][categoryKey]) { 10740 mapMarkersByCategory[mapId][categoryKey] = { 10741 markers: [], 10742 label: categoryLabel, 10743 icon: getCustomIconForCategory([cat], categoryIconOverrides) 10744 }; 10745 } 10746 mapMarkersByCategory[mapId][categoryKey].markers.push(marker); 10747 }); 10748 } 10749 }); 10750 10751 if (markers.length > 0) { 10752 var clusterer = initMarkerClusterer(map, markers, clusterOptions); 10753 mapClusterers[mapId] = clusterer; 10754 } 10755 10756 // Populate the legend if not hidden 10757 if (legendPosition !== 'hidden') { 10758 populateMapLegend(mapId, categorySet, categoryIconOverrides); 10759 } 10760 }) 10761 .catch(function(error) { 10762 console.error('Error fetching environmental effort data:', error); 10763 }); 10764} 10765 10766/** 10767 * Keep same-coordinate Environmental Effort markers independently clickable. 10768 * The visual positions are offset only in the browser; CF coordinates remain 10769 * unchanged. 10770 */ 10771function prepareEnvironmentalEffortMarkerItems(items) { 10772 var markerItems = []; 10773 var coordinateGroups = {}; 10774
10775 items.forEach(function(item) { 10776 if (!item.latitude || !item.longitude) { 10777 console.warn('Skipping item without coordinates:', item.title); 10778 return; 10779 } 10780 10781 var lat = parseFloat(item.latitude); 10782 var lng = parseFloat(item.longitude); 10783 if (isNaN(lat) || isNaN(lng)) { 10784 console.warn('Invalid coordinates for item:', item.title); 10785 return; 10786 } 10787 10788 if (!isItemActive(item)) { 10789 return; 10790 } 10791 10792 var markerItem = { 10793 item: item, 10794 lat: lat, 10795 lng: lng, 10796 position: { lat: lat, lng: lng } 10797 }; 10798 var coordinateKey = lat + ',' + lng; 10799 if (!coordinateGroups[coordinateKey]) { 10800 coordinateGroups[coordinateKey] = []; 10801 } 10802 coordinateGroups[coordinateKey].push(markerItem); 10803 markerItems.push(markerItem); 10804 }); 10805 10806 Object.keys(coordinateGroups).forEach(function(coordinateKey) { 10807 var group = coordinateGroups[coordinateKey]; 10808 if (group.length < 2) { 10809 return; 10810 } 10811 10812 // A CF path provides a stable order, so markers do not swap positions 10813 // between page loads when the GraphQL response order changes. 10814 group.sort(function(first, second) { 10815 var firstPath = first.item._path || first.item.path || first.item.title || ''; 10816 var secondPath = second.item._path || second.item.path || second.item.title || ''; 10817 return firstPath.localeCompare(secondPath); 10818 }); 10819 10820 group.forEach(function(markerItem, groupIndex) { 10821 markerItem.position = getRadialOffsetPosition( 10822 markerItem.lat, 10823 markerItem.lng, 10824 groupIndex, 10825 group.length 10826 ); 10827 }); 10828 }); 10829 10830 return markerItems; 10831} 10832 10833/** 10834 * Spread overlapping markers evenly around their shared coordinate by 15m. 10835 */ 10836function getRadialOffsetPosition(latitude, longitude, index, count) { 10837 if (count < 2) { 10838 return { lat: latitude, lng: longitude }; 10839 } 10840 10841 var offsetMetres = 15; 10842 var metresPerLatitudeDegree = 111320; 10843 var angle = -Math.PI / 2 + ((Math.PI * 2 * index) / count); 10844 var northMetres = Math.cos(angle) * offsetMetres; 10845 var eastMetres = Math.sin(angle) * offsetMetres; 10846 var longitudeScale = metresPerLatitudeDegree * Math.cos(latitude * Math.PI / 180); 10847 10848 return { 10849 lat: latitude + (northMetres / metresPerLatitudeDegree), 10850 lng: longitude + (eastMetres / longitudeScale) 10851 }; 10852} 10853 10854/** 10855 * Build info window HTML content for environmental effort item 10856 */ 10857function buildEnvironmentalEffortInfoWindow(item, categoryIconOverrides) { 10858 var content = '<div class="environmental-effort-infowindow">'; 10859 10860 // Category tags 10861 if (item.category && item.category.length > 0) { 10862 content += '<div class="environmental-effort-infowindow__categories">'; 10863 item.category.forEach(function(cat) { 10864 // Use custom label if available, otherwise use default label 10865 var categoryLabel = getCustomLabelForCategory([cat], categoryIconOverrides) || cat.split(':').pop().replace(/-/g, ' ') || cat; 10866 content += '<p class="environmental-effort-infowindow__category-tag">' + categoryLabel + '</p>'; 10867 }); 10868 content += '</div>'; 10869 } 10870 10871 // Generate unique ID for this item 10872 var uniqueId = 'media-' + Math.random().toString(36).substr(2, 9); 10873 10874 if(item.embed) { 10875 // Embed content - make it clickable to open modal 10876 content += '<div class="environmental-effort-infowindow__embed" id="' + uniqueId + '-preview" style="position: relative; pointer-events: none;">'; 10877 content += item.embed; 10878 // Add invisible overlay to capture clicks 10879 content += '<div class="embed-trigger" data-embed-id="' + uniqueId + '" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer; pointer-events: auto; z-index: 10;"></div>'; 10880 // Store embed content in a hidden element for modal use 10881 content += '<div id="' + uniqueId + '-embed-content" style="display: none;">' + item.embed + '</div>'; 10882 content += '</div>'; 10883 } 10884 else if (item.video && item.video._path) { 10885 // Video exists - make it clickable to open modal 10886 var posterAttr = (item.image && item.image._path) ? ' poster="' + item.image._path + '"' : ''; 10887 10888 content += '<div class="environmental-effort-infowindow__video video-trigger" data-video-id="' + uniqueId + '" style="cursor: pointer; position: relative;">'; 10889 content += '<video id="' + uniqueId + '"' + posterAttr + ' style="display: block; width: 100%;"><source src="' + item.video._path + '" type="video/mp4">Your browser does not support the video tag.</video>'; 10890 content += '<div class="video-play-button" id="play-btn-' + uniqueId + '" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); pointer-events: none;">'; 10891 content += '<svg xmlns="http://www.w3.org/2000/svg" width="60" height="40" viewBox="0 0 60 40" fill="none">'; 10892 content += '<rect width="60" height="40" rx="8" fill="#262C32" fill-opacity="0.9"/>'; 10893 content += '<path d="M38.5 20L25.75 27.7942L25.75 12.2058L38.5 20Z" fill="white"/>'; 10894 content += '</svg>'; 10895 content += '</div>'; 10896 content += '</div>'; 10897 } else if (item.image && item.image._path) { 10898 // Image only (no video) - just show the image without play button 10899 content += '<div class="environmental-effort-infowindow__image"><img src="' + item.image._path + '" alt="' + (item.title || '') + '" /></div>'; 10900 } 10901 10902 // Title 10903 if (item.title) { 10904 content += '<h5 class="environmental-effort-infowindow__title">' + item.title + '</h5>'; 10905 } 10906 10907 // Full Description (HTML content) 10908 if (item.description && item.description.html) { 10909 content += '<div class="environmental-effort-infowindow__description">' + item.description.html + '</div>'; 10910 } 10911 10912 // CTA Link 10913 if (item.ctaLink && item.ctaLabel) { 10914 content += '<a href="' + item.ctaLink + '" target="_blank" class="btn-arrow btn-quinary environmental-effort-infowindow__cta">' + 10915 '<span>' + item.ctaLabel + '</span>' + 10916 '</a>'; 10917 } 10918 10919 content += '</div>'; 10920 return content; 10921} 10922
10923/** 10924 * Initialize Google Maps MarkerClusterer 10925 */ 10926function initMarkerClusterer(map, markers, clusterOptions) { 10927 var clusterer = null; 10928 10929 // Check if markerclusterer library is available 10930 if (typeof markerClusterer !== 'undefined' && markerClusterer.MarkerClusterer) { 10931 clusterer = new markerClusterer.MarkerClusterer({ 10932 map: map, 10933 markers: markers, 10934 renderer: { 10935 render: function({ count, position }) { 10936 return new google.maps.marker.AdvancedMarkerElement({ 10937 position: position, 10938 content: createClusterIcon(count, clusterOptions.color), 10939 zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count 10940 }); 10941 } 10942 } 10943 }); 10944 } else { 10945 // Fallback: Load MarkerClusterer library dynamically 10946 var script = document.createElement('script'); 10947 script.src = 'https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js'; 10948 script.onload = function() { 10949 clusterer = new markerClusterer.MarkerClusterer({ 10950 map: map, 10951 markers: markers, 10952 renderer: { 10953 render: function({ count, position }) { 10954 return new google.maps.marker.AdvancedMarkerElement({ 10955 position: position, 10956 content: createClusterIcon(count, clusterOptions.color), 10957 zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count 10958 }); 10959 } 10960 } 10961 }); 10962 }; 10963 document.head.appendChild(script); 10964 } 10965 10966 return clusterer; 10967} 10968 10969/** 10970 * Create custom cluster icon element 10971 */ 10972function createClusterIcon(count, color) { 10973 var div = document.createElement('div'); 10974 div.className = 'cluster-marker'; 10975 // Apply custom color if provided 10976 if (color && color !== '#0074D9') { 10977 div.style.background = color; 10978 } 10979 div.textContent = count; 10980 return div; 10981} 10982 10983/** 10984 * Populate the map legend with category checkboxes 10985 */ 10986function populateMapLegend(mapId, categorySet, categoryIconOverrides) { 10987 var legendContainer = document.getElementById(mapId + '-legend'); 10988 if (!legendContainer) { 10989 console.warn('Legend container not found for map:', mapId); 10990 return; 10991 } 10992 10993 var categoriesContainer = legendContainer.querySelector('.google-map-legend__categories'); 10994 if (!categoriesContainer) { 10995 console.warn('Categories container not found in legend'); 10996 return; 10997 } 10998 10999 // Clear existing content 11000 categoriesContainer.innerHTML = ''; 11001 11002 // Convert Set to Array and sort alphabetically by label 11003 var categories = Array.from(categorySet).map(function(item) { 11004 return JSON.parse(item); 11005 }).sort(function(a, b) { 11006 return a.label.localeCompare(b.label); 11007 }); 11008 11009 categories.forEach(function(category) { 11010 var categoryItem = document.createElement('div'); 11011 categoryItem.className = 'google-map-legend__category-item'; 11012 11013 // Add icon first (if available) 11014 var iconUrl = getCustomIconForCategory([category.original], categoryIconOverrides); 11015 if (iconUrl) { 11016 var iconImg = document.createElement('img'); 11017 iconImg.src = iconUrl; 11018 iconImg.alt = category.label; 11019 iconImg.className = 'google-map-legend__icon'; 11020 categoryItem.appendChild(iconImg); 11021 } 11022 11023 // Add label text second - use custom label if available 11024 var label = document.createElement('label'); 11025 label.htmlFor = mapId + '-category-' + category.key.replace(/[^a-z0-9]/g, '-'); 11026 label.className = 'google-map-legend__label'; 11027 11028 var labelText = document.createElement('span'); 11029 labelText.className = 'google-map-legend__label-text'; 11030 // Check for custom label override 11031 var displayLabel = getCustomLabelForCategory([category.original], categoryIconOverrides) || category.label; 11032 labelText.textContent = displayLabel; 11033 label.appendChild(labelText); 11034 11035 categoryItem.appendChild(label); 11036 11037 // Add checkbox last 11038 var checkbox = document.createElement('input'); 11039 checkbox.type = 'checkbox'; 11040 checkbox.checked = true; 11041 checkbox.id = mapId + '-category-' + category.key.replace(/[^a-z0-9]/g, '-'); 11042 checkbox.className = 'google-map-legend__checkbox'; 11043 checkbox.dataset.mapId = mapId; 11044 checkbox.dataset.categoryKey = category.key; 11045 11046 categoryItem.appendChild(checkbox); 11047 categoriesContainer.appendChild(categoryItem); 11048 11049 // Add event listener for checkbox 11050 checkbox.addEventListener('change', function() { 11051 toggleCategoryMarkers(this.dataset.mapId, this.dataset.categoryKey, this.checked); 11052 }); 11053 }); 11054} 11055 11056/** 11057 * Toggle visibility of markers for a specific category 11058 */ 11059function toggleCategoryMarkers(mapId, categoryKey, visible) { 11060 var categoryData = mapMarkersByCategory[mapId] && mapMarkersByCategory[mapId][categoryKey]; 11061 if (!categoryData) { 11062 console.warn('No markers found for category:', categoryKey); 11063 return; 11064 } 11065 11066 updateClusterer(mapId); 11067} 11068 11069/** 11070 * Get map index by ID 11071 */ 11072function getMapIndexById(mapId) { 11073 var mapElements = document.querySelectorAll('.google-map-component'); 11074 for (var i = 0; i < mapElements.length; i++) { 11075 if (mapElements[i].id === mapId) { 11076 return i; 11077 } 11078 } 11079 return 0; 11080} 11081 11082/** 11083 * Update clusterer with visible markers 11084 */ 11085function updateClusterer(mapId) { 11086 var clusterer = mapClusterers[mapId]; 11087 if (!clusterer) return; 11088 11089 var visibleMarkers = []; 11090 var categoriesData = mapMarkersByCategory[mapId]; 11091 var processedMarkers = new Set(); 11092 var map = mapById[mapId]; // Use mapById for reliable lookup 11093 11094 // Get the legend container for this specific map to scope checkbox lookups 11095 var legendContainer = document.getElementById(mapId + '-legend'); 11096 11097 if (categoriesData) {
11098 Object.keys(categoriesData).forEach(function(categoryKey) { 11099 // Look up checkbox within the specific map's legend container to avoid cross-map conflicts 11100 var checkboxId = mapId + '-category-' + categoryKey.replace(/[^a-z0-9]/g, '-'); 11101 var checkbox = legendContainer 11102 ? legendContainer.querySelector('#' + CSS.escape(checkboxId)) 11103 : document.getElementById(checkboxId); 11104 11105 if (checkbox && checkbox.checked) { 11106 categoriesData[categoryKey].markers.forEach(function(marker) { 11107 if (!processedMarkers.has(marker)) { 11108 processedMarkers.add(marker); 11109 visibleMarkers.push(marker); 11110 } 11111 }); 11112 } 11113 }); 11114 } 11115 11116 // Clear all markers from clusterer and map 11117 clusterer.clearMarkers(); 11118 11119 // Hide all markers first 11120 if (categoriesData) { 11121 Object.keys(categoriesData).forEach(function(categoryKey) { 11122 categoriesData[categoryKey].markers.forEach(function(marker) { 11123 marker.setMap(null); 11124 }); 11125 }); 11126 } 11127 11128 // Show and add only visible markers to the clusterer 11129 visibleMarkers.forEach(function(marker) { 11130 marker.setMap(map); 11131 }); 11132 11133 clusterer.addMarkers(visibleMarkers); 11134} 11135 11136function createMarkers(index) { 11137 var data = document.querySelectorAll('.google-map-component')[index].getAttribute('data-json'); 11138 11139 11140 if (data == undefined || data == null) { 11141 return; 11142 } 11143 11144 11145 try { 11146 data = JSON.parse(data); 11147 console.log(data) 11148 } catch (e) { 11149 console.warn('Map points could not be parsed:', e); 11150 return; 11151 } 11152 11153 data.forEach(function (point, i) { 11154 console.log(point); 11155 11156 var pointTitle = point.title != 'null' ? unescapeToHtml(point.title) : ''; 11157 var pointIcon = point.customIcon != 'null' ? unescapeToHtml(point.customIcon) : ''; 11158 var pointContent = point.content != 'null' ? unescapeToHtml(point.content) : ''; 11159 console.log(pointTitle) 11160 11161 var infowindowContent = ""; 11162 infowindowContent = pointTitle ? infowindowContent + "<h5 class='marker--title mb-3'>" + pointTitle + '</h5>' : infowindowContent; 11163 infowindowContent = pointContent ? infowindowContent + "<p class='marker-content'>" + pointContent + '</p>' : infowindowContent; 11164 11165 var infowindow = new google.maps.InfoWindow({ 11166 content: infowindowContent, 11167 }); 11168 11169 var markerElement = null; 11170 11171 // Create the element for the AdvancedMarkerElement 11172 if (pointIcon) { 11173 markerElement = document.createElement('div'); 11174 markerElement.classList.add('custom-marker'); 11175 markerElement.innerHTML = "<img src='" + pointIcon + "' alt='" + pointTitle + "' />"; 11176 } 11177 11178 var marker = new google.maps.marker.AdvancedMarkerElement({ 11179 position: { 11180 lat: parseFloat(point.latitude), 11181 lng: parseFloat(point.longitude), 11182 }, 11183 title: pointTitle, 11184 content: markerElement, // Set the custom marker content 11185 map: mapList[index], // Add to the map 11186 }); 11187 11188 // Add a click listener for each marker to open the info window 11189 marker.addListener('click', function () { 11190 infowindow.open({ 11191 map: mapList[index], 11192 anchor: marker, 11193 shouldFocus: false, 11194 }); 11195 }); 11196 }); 11197} 11198 11199/** 11200 * Play environmental effort video - hides play button and shows controls 11201 * On desktop, shows video in a popup modal with grey opaque background 11202 */ 11203function playEnvironmentalVideo(videoId) { 11204 var video = document.getElementById(videoId); 11205 var playBtn = document.getElementById('play-btn-' + videoId); 11206 11207 if (!video) return; 11208 11209 // Check if desktop (screen width > 768px) 11210 var isDesktop = window.innerWidth > 768; 11211 11212 if (isDesktop) { 11213 // Create modal overlay for desktop 11214 var modal = document.createElement('div'); 11215 modal.id = 'video-modal-' + videoId; 11216 modal.className = 'environmental-video-modal'; 11217 11218 // Create modal content container 11219 var modalContent = document.createElement('div'); 11220 modalContent.className = 'environmental-video-modal__content'; 11221 11222 // Create close button 11223 var closeBtn = document.createElement('button'); 11224 closeBtn.className = 'environmental-video-modal__close'; 11225 closeBtn.innerHTML = '×'; 11226 closeBtn.onclick = function() { 11227 closeVideoModal(videoId); 11228 }; 11229 11230 // Clone the video for the modal 11231 var modalVideo = document.createElement('video'); 11232 modalVideo.id = 'modal-video-' + videoId; 11233 modalVideo.className = 'environmental-video-modal__media-content'; 11234 modalVideo.src = video.querySelector('source').src; 11235 modalVideo.controls = true; 11236 11237 // Assemble modal 11238 modalContent.appendChild(closeBtn); 11239 modalContent.appendChild(modalVideo); 11240 modal.appendChild(modalContent); 11241 11242 // Close modal when clicking on background 11243 modal.onclick = function(e) { 11244 if (e.target === modal) { 11245 closeVideoModal(videoId); 11246 } 11247 }; 11248 11249 // Close modal on escape key 11250 document.addEventListener('keydown', function escHandler(e) { 11251 if (e.key === 'Escape') { 11252 closeVideoModal(videoId); 11253 document.removeEventListener('keydown', escHandler); 11254 } 11255 }); 11256 11257 // Add modal to body 11258 document.body.appendChild(modal); 11259 11260 // Pause original video if playing 11261 video.pause(); 11262 11263 } else { 11264 // Mobile behavior - play inline 11265 video.setAttribute('controls', 'controls'); 11266 video.play(); 11267 11268 // Hide the play button 11269 if (playBtn) { 11270 playBtn.style.display = 'none'; 11271 } 11272 11273 // Listen for video pause/end to show play button again 11274 video.addEventListener('pause', function() { 11275 if (playBtn) { 11276 playBtn.style.display = 'block'; 11277 } 11278 }); 11279 11280 video.addEventListener('ended', function() { 11281 if (playBtn) { 11282 playBtn.style.display = 'block'; 11283 } 11284 // Hide controls when video ends 11285 video.removeAttribute('controls'); 11286 }); 11287 11288 // Hide play button while playing 11289 video.addEventListener('play', function() { 11290 if (playBtn) { 11291 playBtn.style.display = 'none'; 11292 } 11293 }); 11294 } 11295} 11296 11297/** 11298 * Close the video modal popup 11299 */ 11300function closeVideoModal(videoId) { 11301 var modal = document.getElementById('video-modal-' + videoId); 11302 if (modal) { 11303 var modalVideo = document.getElementById('modal-video-' + videoId); 11304 if (modalVideo) { 11305 modalVideo.pause(); 11306 } 11307 modal.remove(); 11308 } 11309} 11310 11311/** 11312 * Open modal for embed content 11313 */ 11314function openEmbedModal(uniqueId, triggerElement) { 11315 // Get the embed content from the hidden element 11316 var embedContentElement = document.getElementById(uniqueId + '-embed-content'); 11317 if (!embedContentElement) { 11318 console.warn('Embed content not found for:', uniqueId); 11319 return; 11320 } 11321 11322 // Check if desktop (screen width > 768px) 11323 var isDesktop = window.innerWidth > 768; 11324 11325 if (isDesktop) { 11326 // Create modal overlay for desktop 11327 var modal = document.createElement('div'); 11328 modal.id = 'embed-modal-' + uniqueId; 11329 modal.className = 'environmental-video-modal'; 11330 11331 // Create modal content container 11332 var modalContent = document.createElement('div'); 11333 modalContent.className = 'environmental-video-modal__content'; 11334 11335 // Create close button 11336 var closeBtn = document.createElement('button'); 11337 closeBtn.className = 'environmental-video-modal__close'; 11338 closeBtn.innerHTML = '×'; 11339 closeBtn.onclick = function() { 11340 closeEmbedModal(uniqueId); 11341 }; 11342 11343 // Create wrapper for embed content 11344 var embedWrapper = document.createElement('div'); 11345 embedWrapper.className = 'environmental-video-modal__embed'; 11346 embedWrapper.innerHTML = embedContentElement.innerHTML; 11347 11348 // Apply shared media-content class to iframes for consistent styling 11349 var iframes = embedWrapper.querySelectorAll('iframe');
11350 iframes.forEach(function(iframe) { 11351 iframe.classList.add('environmental-video-modal__media-content'); 11352 }); 11353 11354 // Assemble modal 11355 modalContent.appendChild(closeBtn); 11356 modalContent.appendChild(embedWrapper); 11357 modal.appendChild(modalContent); 11358 11359 // Close modal when clicking on background 11360 modal.onclick = function(e) { 11361 if (e.target === modal) { 11362 closeEmbedModal(uniqueId); 11363 } 11364 }; 11365 11366 // Close modal on escape key 11367 document.addEventListener('keydown', function escHandler(e) { 11368 if (e.key === 'Escape') { 11369 closeEmbedModal(uniqueId); 11370 document.removeEventListener('keydown', escHandler); 11371 } 11372 }); 11373 11374 // Add modal to body 11375 document.body.appendChild(modal); 11376 } else { 11377 // Mobile behavior - play inline by enabling pointer events on the embed 11378 var previewElement = document.getElementById(uniqueId + '-preview'); 11379 if (previewElement) { 11380 // Remove pointer-events: none to allow interaction with the embed 11381 previewElement.style.pointerEvents = 'auto'; 11382 11383 // Hide the overlay trigger 11384 var overlayTrigger = previewElement.querySelector('.embed-trigger'); 11385 if (overlayTrigger) { 11386 overlayTrigger.style.display = 'none'; 11387 } 11388 } 11389 } 11390} 11391 11392/** 11393 * Close the embed modal popup 11394 */ 11395function closeEmbedModal(uniqueId) { 11396 var modal = document.getElementById('embed-modal-' + uniqueId); 11397 if (modal) { 11398 modal.remove(); 11399 } 11400} 11401 11402/** 11403 * Check if an environmental effort item is active based on date range 11404 */ 11405function isItemActive(item) { 11406 if (!item.startDate && !item.endDate) { 11407 return true; // No date range specified, item is active 11408 } 11409 11410 var now = new Date(); 11411 var start = item.startDate ? new Date(item.startDate) : null; 11412 var end = item.endDate ? new Date(item.endDate) : null; 11413 11414 // Check if current date is within the start and end date range 11415 return (!start || now >= start) && (!end || now <= end); 11416} 11417 11418/** 11419 * Get custom icon URL for a specific category 11420 */ 11421function getCustomIconForCategory(categories, overrides) { 11422 if (!categories || !overrides || Object.keys(overrides).length === 0) { 11423 return null; 11424 } 11425 11426 // Categories can be an array, so check each one 11427 var categoryArray = Array.isArray(categories) ? categories : [categories]; 11428 11429 11430 for (const element of categoryArray) { 11431 var cat = element; 11432 if (!cat) continue; 11433 11434 // Try exact match first (case-insensitive) 11435 var lowerCat = cat.toLowerCase(); 11436 11437 if (overrides[lowerCat]) { 11438 return overrides[lowerCat].icon; 11439 } 11440 11441 // If not found, try matching just the suffix after colon (e.g., "business_faq" from "APC:business_faq") 11442 if (cat.indexOf(':') > -1) { 11443 var suffix = cat.split(':').pop().toLowerCase(); 11444 11445 if (overrides[suffix]) { 11446 return overrides[suffix].icon; 11447 } 11448 } 11449 } 11450 11451 return null; 11452} 11453 11454/** 11455 * Render a category icon inside a white circular marker background. 11456 */ 11457function createCircularMarkerIcon(iconUrl, borderColor, borderWidth, hasShadow) { 11458 return new Promise(function(resolve, reject) { 11459 var image = new Image(); 11460 11461 image.onload = function() { 11462 var canvas = document.createElement('canvas'); 11463 var size = hasShadow ? 68 : 48; 11464 var center = size / 2; 11465 canvas.width = size; 11466 canvas.height = size; 11467 11468 var context = canvas.getContext('2d'); 11469 if (hasShadow) { 11470 // Paint the shadow first, then repaint the badge without a shadow. 11471 // That keeps the white marker face completely white. 11472 context.save(); 11473 context.shadowColor = 'rgba(0, 0, 0, 0.32)'; 11474 context.shadowBlur = 8; 11475 context.shadowOffsetY = 3; 11476 context.beginPath(); 11477 context.arc(center, center, 23, 0, Math.PI * 2); 11478 context.fillStyle = '#ffffff'; 11479 context.fill(); 11480 context.restore(); 11481 } 11482 context.beginPath(); 11483 context.arc(center, center, 23, 0, Math.PI * 2); 11484 context.fillStyle = '#ffffff'; 11485 context.fill(); 11486 context.lineWidth = borderWidth || 1.5; 11487 context.strokeStyle = borderColor || '#d1d5db'; 11488 context.stroke(); 11489 11490 context.drawImage(image, center - 14, center - 14, 28, 28); 11491 resolve(canvas.toDataURL('image/png')); 11492 }; 11493 11494 image.onerror = reject; 11495 image.src = iconUrl; 11496 }); 11497} 11498 11499/** 11500 * Get custom label for a specific category 11501 */ 11502function getCustomLabelForCategory(categories, overrides) { 11503 if (!categories || !overrides || Object.keys(overrides).length === 0) { 11504 return null; 11505 } 11506 11507 // Categories can be an array, so check each one 11508 var categoryArray = Array.isArray(categories) ? categories : [categories]; 11509 11510 for (const element of categoryArray) { 11511 var cat = element; 11512 if (!cat) continue; 11513 11514 // Try exact match first (case-insensitive) 11515 var lowerCat = cat.toLowerCase(); 11516 11517 if (overrides[lowerCat] && overrides[lowerCat].label) { 11518 return overrides[lowerCat].label; 11519 } 11520 11521 // If not found, try matching just the suffix after colon (e.g., "business_faq" from "APC:business_faq") 11522 if (cat.indexOf(':') > -1) { 11523 var suffix = cat.split(':').pop().toLowerCase(); 11524 11525 if (overrides[suffix] && overrides[suffix].label) { 11526 return overrides[suffix].label; 11527 } 11528 } 11529 } 11530 11531 return null; 11532} 11533 11534// Event delegation for video and embed triggers 11535document.addEventListener('click', function(e) { 11536 // Handle video triggers 11537 if (e.target.closest('.video-trigger')) { 11538 var videoTrigger = e.target.closest('.video-trigger'); 11539 var videoId = videoTrigger.dataset.videoId; 11540 if (videoId) { 11541 playEnvironmentalVideo(videoId); 11542 } 11543 } 11544 11545 // Handle embed triggers 11546 if (e.target.closest('.embed-trigger')) { 11547 var embedTrigger = e.target.closest('.embed-trigger'); 11548 var embedId = embedTrigger.dataset.embedId; 11549 if (embedId) { 11550 openEmbedModal(embedId); 11551 } 11552 } 11553 11554 // Handle modal close buttons 11555 if (e.target.closest('.environmental-video-modal__close')) { 11556 var modal = e.target.closest('.environmental-video-modal'); 11557 if (modal) { 11558 if (modal.id.startsWith('video-modal-')) { 11559 var videoId = modal.id.replace('video-modal-', ''); 11560 closeVideoModal(videoId); 11561 } else if (modal.id.startsWith('embed-modal-')) { 11562 var embedId = modal.id.replace('embed-modal-', ''); 11563 closeEmbedModal(embedId); 11564 } 11565 } 11566 } 11567 11568 // Handle modal background clicks 11569 if (e.target.classList.contains('environmental-video-modal')) { 11570 var modal = e.target; 11571 if (modal.id.startsWith('video-modal-')) { 11572 var videoId = modal.id.replace('video-modal-', ''); 11573 closeVideoModal(videoId); 11574 } else if (modal.id.startsWith('embed-modal-')) { 11575 var embedId = modal.id.replace('embed-modal-', ''); 11576 closeEmbedModal(embedId); 11577 } 11578 } 11579}); 11580 11581// Event listener for Escape key to close modals 11582document.addEventListener('keydown', function(e) { 11583 if (e.key === 'Escape') { 11584 var videoModals = document.querySelectorAll('[id^="video-modal-"]');
11585 videoModals.forEach(function(modal) { 11586 var videoId = modal.id.replace('video-modal-', ''); 11587 closeVideoModal(videoId); 11588 }); 11589 11590 var embedModals = document.querySelectorAll('[id^="embed-modal-"]'); 11591 embedModals.forEach(function(modal) { 11592 var embedId = modal.id.replace('embed-modal-', ''); 11593 closeEmbedModal(embedId); 11594 }); 11595 } 11596}); 11597 11598// Re-attach video trigger handlers for dynamically created content 11599function attachVideoTriggerHandlers() { 11600 var videoTriggers = document.querySelectorAll('.video-trigger'); 11601 videoTriggers.forEach(function(trigger) { 11602 var videoId = trigger.dataset.videoId; 11603 if (videoId) { 11604 trigger.addEventListener('click', function() { 11605 playEnvironmentalVideo(videoId); 11606 }); 11607 } 11608 }); 11609} 11610 11611// Re-attach embed trigger handlers for dynamically created content 11612function attachEmbedTriggerHandlers() { 11613 var embedTriggers = document.querySelectorAll('.embed-trigger'); 11614 embedTriggers.forEach(function(trigger) { 11615 var embedId = trigger.dataset.embedId; 11616 if (embedId) { 11617 trigger.addEventListener('click', function() { 11618 openEmbedModal(embedId); 11619 }); 11620 } 11621 }); 11622} 11623 11624let isModalOpen = false; 11625let modalOpenedFirst = null; 11626let disableBackgroundClick = false; 11627let savedScrollY = 0; 11628 11629$(document).ready(() => { 11630 const modals = $('.generalmodal'); 11631 11632 initializeModalSystem(); 11633 11634 // Last line of defence: anything that moves the window while a modal is open 11635 // gets snapped back. Modal content scrolls inside .modal-container, so the 11636 // window position is never meant to change here. 11637 $(window).on('scroll.generalmodal', () => { 11638 if (!isModalOpen) return; 11639 if (window.scrollY === savedScrollY) return; 11640 stopScrollAnimations(); 11641 window.scrollTo(0, savedScrollY); 11642 }); 11643 11644 function initializeModalSystem() { 11645 checkInitialHash(); 11646 setupHashChangeListener(); 11647 setupModalExitButtons(); 11648 setupBackgroundClick(); 11649 setupModalLinkSwitching(); 11650 trackMouseOverModalContainer(); 11651 } 11652 11653 function checkInitialHash() { 11654 const hash = window.location.hash; 11655 if (hash && hash !== '#') { 11656 const targetModal = findModalByHash(hash); 11657 if (targetModal) openModal(targetModal); 11658 } 11659 } 11660 11661 function setupHashChangeListener() { 11662 $(window).on('hashchange', () => { 11663 handleModalSwitch(window.location.hash); 11664 }); 11665 } 11666 11667 function setupModalExitButtons() { 11668 $(document).on('click', '.modal-exit-button.icon-x', function (e) { 11669 e.preventDefault(); 11670 const backgroundEl = $(this).closest('.background'); 11671 closeModal(backgroundEl); 11672 }); 11673 } 11674 11675 function setupBackgroundClick() { 11676 $(document).on('click', '.background', function () { 11677 if (disableBackgroundClick) return; 11678 if ($(this).hasClass('restricted')) return; 11679 closeModal($(this)); 11680 }); 11681 } 11682 11683 function trackMouseOverModalContainer() { 11684 $(document).on('mouseenter', '.modal-container', () => { 11685 disableBackgroundClick = true; 11686 }); 11687 $(document).on('mouseleave', '.modal-container', () => { 11688 disableBackgroundClick = false; 11689 }); 11690 } 11691 11692 function setupModalLinkSwitching() { 11693 $(document).on('click', '.modal-container a[href^="#"]', function (e) { 11694 const href = $(this).attr('href'); 11695 if (!href || href.length <= 1) return; 11696 11697 e.preventDefault(); 11698 11699 // pushState rather than assigning location.hash: assigning the hash is a 11700 // real fragment navigation, and .modal-container carries id="anchorId", 11701 // so the browser would scroll that target into view and snap the page. 11702 if (window.location.hash !== href) { 11703 history.pushState(null, null, href); 11704 } 11705 handleModalSwitch(href); 11706 }); 11707 } 11708 11709 function handleModalSwitch(newHash) { 11710 if (!newHash || newHash === '#') { 11711 if (isModalOpen) { 11712 closeModal(modalOpenedFirst); 11713 } 11714 return; 11715 } 11716 11717 const targetModal = findModalByHash(newHash); 11718 if (!targetModal) { 11719 if (isModalOpen) { 11720 closeModal(modalOpenedFirst, { preserveHash: true }); 11721 } 11722 return; 11723 } 11724 11725 const switchingModals = isModalOpen && modalOpenedFirst !== targetModal; 11726 11727 // Callers own the URL: hashchange arrives with it already updated, and click 11728 // handlers pushState before calling. preserveScrollLock keeps the page pinned 11729 // and the hash intact across the close/open pair. 11730 if (switchingModals) { 11731 closeModal(modalOpenedFirst, { preserveScrollLock: true }); 11732 } 11733 11734 if (!isModalOpen || switchingModals) { 11735 openModal(targetModal); 11736 } 11737} 11738 function findModalByHash(hash) { 11739 for (const modal of modals) { 11740 const background = $(modal).find('.background')[0]; 11741 if ('#' + $(background).attr('anchorId') === hash) { 11742 return background; 11743 } 11744 } 11745 return null; 11746 } 11747 11748 // The site-wide scroll script in global.min.js binds its own click handler to 11749 // every in-page anchor and queues $('html,body').animate({scrollTop: ...}) 11750 // before our delegated handlers run, so preventDefault cannot cancel it. 11751 function stopScrollAnimations() { 11752 $('html, body').stop(true, false); 11753 } 11754 11755 function lockPageScroll() { 11756 // Guard so switching modals (which re-locks without unlocking) can't 11757 // overwrite the position captured when the first modal opened. 11758 if ($('html').hasClass('generalmodal-scroll-lock')) return; 11759 savedScrollY = window.scrollY; 11760 $('html').addClass('generalmodal-scroll-lock'); 11761 stopScrollAnimations(); 11762 // Both handlers run inside the same click, so a single stop() can lose the 11763 // race against the animate() call that follows it. 11764 requestAnimationFrame(() => { 11765 if (!$('html').hasClass('generalmodal-scroll-lock')) return; 11766 stopScrollAnimations(); 11767 window.scrollTo(0, savedScrollY); 11768 }); 11769 } 11770 11771 function unlockPageScroll() { 11772 stopScrollAnimations(); 11773 $('html').removeClass('generalmodal-scroll-lock'); 11774 window.scrollTo(0, savedScrollY); 11775 } 11776 11777 function openModal(target) { 11778 $(target).css('display', 'flex'); 11779 lockPageScroll(); 11780 isModalOpen = true; 11781 modalOpenedFirst = target; 11782 } 11783 11784 function closeModal(backgroundEl, { preserveScrollLock = false, preserveHash = false } = {}) { 11785 resetEmbeddedVideos($(backgroundEl)); 11786 $(backgroundEl).css('display', 'none'); 11787 11788 isModalOpen = false;
11789 modalOpenedFirst = null; 11790 11791 if (!preserveScrollLock) { 11792 unlockPageScroll(); 11793 } 11794 11795 if (!preserveHash && !preserveScrollLock && window.location.hash !== '') { 11796 const baseUri = location.origin + location.pathname + location.search; 11797 history.replaceState(null, null, baseUri); 11798 } 11799 } 11800 11801 11802 function resetEmbeddedVideos(modal) { 11803 try { 11804 modal.find('iframe').each(function () { 11805 $(this).attr('src', $(this).attr('src')); 11806 }); 11807 } catch (e) { 11808 // Silent fail 11809 } 11810 } 11811 11812 // Expose API for anchor.js coordination 11813 window.generalModalAPI = { 11814 openModal: function(modalId) { 11815 var target = findModalByHash('#' + modalId); 11816 if (target) openModal(target); 11817 return target; 11818 }, 11819 closeModal: function(options) { 11820 if (modalOpenedFirst) closeModal(modalOpenedFirst, options); 11821 }, 11822 isOpen: function() { return isModalOpen; }, 11823 getCurrentModal: function() { return modalOpenedFirst; }, 11824 findModalByHash: findModalByHash, 11825 switchTo: handleModalSwitch 11826 }; 11827}); 11828 11829$(function(){ 11830 11831 var categoryOption = ''; 11832 $('.gallerygrid__item img.modal-trigger').on('click touch', function () { 11833 var selectedIndex; 11834 var col = $(this).closest('.gallerygrid__col'); 11835 if (categoryOption != '') { 11836 selectedIndex = col.index('div.gallerygrid__col.' + categoryOption); 11837 } else { 11838 selectedIndex = col.index('div.gallerygrid__col'); 11839 } 11840 11841 var modalIndex = 0; 11842 var gallerygridComponent = $(this).closest('.gallerygrid'); 11843 $('.gallerygrid').each(function (i) { 11844 if ($(this).is(gallerygridComponent)) { 11845 modalIndex = i; 11846 } 11847 }); 11848 carousels[modalIndex].data('flexslider').flexAnimate(selectedIndex); 11849 11850 // show modal 11851 $(this).closest('.gallerygrid').find('.gallerygrid__modal').addClass('active'); 11852 }); 11853 11854 var originalCarouselSlides = [], 11855 originalThumbnailSlides = []; 11856 $('.gallerygrid__carousel').each(function (i) { 11857 var slides = {}; 11858 $(this).attr('id', 'flexslider__carousel-' + (i + 1)); 11859 slides.id = $(this).attr('id'); 11860 slides.markup = $(this).find('.slides').html(); 11861 originalCarouselSlides.push(slides); 11862 }); 11863 $('.gallerygrid__thumbnails').each(function (i) { 11864 var slides = {}; 11865 $(this).attr('id', 'flexslider__thumbnails-' + (i + 1)); 11866 slides.id = $(this).attr('id'); 11867 slides.markup = $(this).find('.slides').html(); 11868 originalThumbnailSlides.push(slides); 11869 }); 11870 11871 $('.gallerygrid__select select option').each(function () { 11872 var option = $(this).attr('data-option'); 11873 option = option.replace(/ /g, '-'); 11874 $(this).attr('data-option', option); 11875 }); 11876 11877 $('.gallerygrid__select select').bind({ 11878 "create": function (ev, obj) { 11879 var $el = $(this), 11880 val = obj.dropdownOption.data('val'), 11881 option = obj.dropdownOption.data('option'); 11882 setCategoryLabel($el, val); 11883 filterGalleryResults($el, option); 11884 }, 11885 "changed": function (ev, obj) { 11886 var $el = $(this), 11887 val = obj.dropdownOption.data('val'), 11888 option = obj.dropdownOption.data('option'); 11889 setCategoryLabel($el, val); 11890 filterGalleryResults($el, option); 11891 } 11892 }); 11893 var categorySelect = $('.gallerygrid__select select').selectBoxIt(); 11894 11895 function setCategoryLabel($el, categoryText) { 11896 $el.closest('.gallerygrid__select').find('.gallerygrid__category').text(categoryText); 11897 } 11898 11899 function filterGalleryResults($el, category) { 11900 // track category 11901 categoryOption = category; 11902 11903 var gallerygridComponent = $el.closest('.gallerygrid'); 11904 11905 // filter grid 11906 var gridItems = gallerygridComponent.find('.gallerygrid__col'); 11907 gridItems.each(function () { 11908 if (category != '') { 11909 if (!$(this).hasClass(category)) { 11910 $(this).hide(); 11911 } else { 11912 $(this).show(); 11913 } 11914 } else { 11915 $(this).show(); 11916 } 11917 }); 11918 11919 // handle pagination 11920 var $categoryItems = gallerygridComponent.find('.gallerygrid__col'); 11921 if (category != '') { 11922 $categoryItems = gallerygridComponent.find('.gallerygrid__col.' + category); 11923 } 11924 var $pagination = gallerygridComponent.find('.gallerygrid__pagination'); 11925 initPagination($pagination, $categoryItems, 16); 11926
11927 // filter modal carousels. reset their markup, then detach slides with remove() 11928 var thisCarousel = gallerygridComponent.find('.gallerygrid__carousel'); 11929 for (var i = 0; i < originalCarouselSlides.length; i++) { 11930 if (originalCarouselSlides[i].id == thisCarousel.attr('id')) { 11931 thisCarousel.find('.slides').html(originalCarouselSlides[i].markup); 11932 } 11933 } 11934 var thisThumbnails = gallerygridComponent.find('.gallerygrid__thumbnails'); 11935 for (var i = 0; i < originalThumbnailSlides.length; i++) { 11936 if (originalThumbnailSlides[i].id == thisThumbnails.attr('id')) { 11937 thisThumbnails.find('.slides').html(originalThumbnailSlides[i].markup); 11938 } 11939 } 11940 var slides = gallerygridComponent.find('.slide'); 11941 slides.each(function (i) { 11942 if (category != '') { 11943 if (!$(this).hasClass(category)) { 11944 $(this).remove(); 11945 } 11946 } 11947 }); 11948 11949 for (i = 0; i < carousels.length; i++) { 11950 var options = carousels[i].data('flexslider').vars; 11951 carousels[i].removeData('flexslider'); 11952 carousels[i] = carousels[i].flexslider(options); 11953 11954 options = thumbnailCarousels[i].data('flexslider').vars; 11955 thumbnailCarousels[i].removeData('flexslider'); 11956 thumbnailCarousels[i] = thumbnailCarousels[i].flexslider(options); 11957 11958 carousels[i].data('flexslider').flexAnimate(0); 11959 thumbnailCarousels[i].data('flexslider').flexAnimate(0); 11960 } 11961 } 11962}); 11963$(document).ready(function () { 11964 var date = /\d{4}/.exec(Date())[0]; 11965 var defaultText = " Southern Company. Use constitutes acceptance of General Website." 11966 11967 //a default copyright if no message is specified in dialogue 11968 var defaultElement = document.getElementById('footer-copyright-default'); 11969 defaultElement ? defaultElement.innerHTML = "© " + date + defaultText : null; 11970 11971 //setting year for copyright if a message is specified in dialogue 11972 var customElement = document.getElementById('footer-copyright-custom'); 11973 var text = "© " + date + " "; 11974 customElement ? customElement.insertAdjacentHTML("afterbegin", text) : null; 11975}) 11976$(function () { 11977 $(".featured--article-rail[data-type='latest'], .featured--article-rail[data-type='latestPublished']").each(function () { 11978 var $this = $(this); 11979 var data = $this.find('.featured-article-data'); 11980 var cols = data.attr('data-columns'); 11981 var root = $this; 11982 var showAuthor = data.attr('data-show-author') === 'true'; 11983 11984 var filter = $this.attr('data-type') === 'latestPublished' ? 'most-recent-published' : 'most-recent'; 11985 11986 $.get('/bin/southern/global/latestarticles', { 11987 tags: data.attr('data-tags'), 11988 path: data.attr('data-path'), 11989 articleStart: 0, 11990 limit: 2, 11991 filter: filter, 11992 }).done(function (data) { 11993 var pages = data.pages; 11994 pages = pages.filter(function (s) { 11995 return s.path != window.location.href; 11996 }); 11997 11998 if (pages.length == 0) { 11999 return; 12000 } 12001 12002 var page = pages[0]; 12003 var redirect = page.customRedirect ? page.customRedirect : page.path; 12004 var imageAlt = escapeHtmlAttr(page.articlePromoImageAlt); 12005 var article = $( 12006 '<div class="featured--article-image-section" > <a href="' + 12007 redirect + 12008 '" ><img class="featured--article-image sr-only" src="' + 12009 page.articlePromoImage + 12010 '" alt="' + 12011 imageAlt + 12012 '"/><figure class="fixedratio" style="background-image: url(' + 12013 page.articlePromoImage + 12014 ');"></figure></a></div><div class="featured--article-tag">' + 12015 page.primaryTag + 12016 '</div><div class="featured--article-title-section"><a href="' + 12017 redirect + 12018 '" class="featured--article-title">' + 12019 page.title + 12020 '</a></div>' 12021 ); 12022 12023 if (page.tagClasses) { 12024 root.addClass(page.tagClasses); 12025 } 12026 12027 root.append(article); 12028 12029 root.append( 12030 $('<div class="featured--article-desc"></div>').append( 12031 $('<p></p>').text(page.description) 12032 ) 12033 ); 12034 12035 if (showAuthor && page.author) { 12036 var $author = $('<div class="featured--article-author"></div>'); 12037 if (page.authorLink) { 12038 $author.append( 12039 $('<a class="featured--article-author-link"></a>') 12040 .attr('href', page.authorLink) 12041 .attr('aria-label', page.author) 12042 .text(page.author) 12043 ); 12044 } else { 12045 $author.text(page.author); 12046 } 12047 root.append($author); 12048 } 12049 }); 12050 }); 12051 12052 function escapeHtmlAttr(value) { 12053 if (value == null || value === '') { 12054 return ''; 12055 } 12056 return String(value) 12057 .replace(/&/g, '&') 12058 .replace(/"/g, '"') 12059 .replace(/'/g, ''') 12060 .replace(/</g, '<') 12061 .replace(/>/g, '>'); 12062 } 12063}); 12064 12065$(function () { 12066 $('.deep-nav').each(function () { 12067 var $nav = $(this), 12068 $contents = $nav.find('.deep-nav--contents'), 12069 $entries = $nav.find('.deep-nav--entries .deep-nav--entry'), 12070 $remainder = $nav.find('.deep-nav--remainder'), 12071 $menu_trigger = $nav.find('.deep-nav--more-entry, .deep-nav--sections-entry'), 12072 $more_list = $nav.find('.deep-nav--more-entries'); 12073 12074 if ($entries.length < 6 && $(window).width() > 991) { 12075 $('.deep-nav--remainder').addClass('hidden'); 12076 } 12077 else { 12078 $('.deep-nav--remainder').removeClass('hidden'); 12079 } 12080 12081 /* We'll calculate these in a minute */ 12082 var $dropdown_entries; 12083 12084 function component_onready() { 12085 $dropdown_entries = clone_entries_to_more(); 12086 12087 /* Check entry visibility now and on resize. */ 12088 update_entry_visibility(); 12089 $(window).resize(function () { 12090 update_entry_visibility(); 12091 }); 12092 12093 /* Expose/hide the "More" menu. */ 12094 $menu_trigger.click(function () { 12095 $remainder.toggleClass('visible'); 12096 return false;
12097 }); 12098 12099 /* When a "More" link is clicked, close the menu (in case it's an 12100 * internal link */ 12101 $more_list.find('a').click(function () { 12102 $remainder.removeClass('active'); 12103 // and continue with the default handler 12104 }); 12105 12106 /* Now that we've set everything up, we can safely set our overflow to 12107 * visible so that we can see menu contents. Previously the overfslow 12108 * was hidden to hide unprocessed entries that had wrapped. */ 12109 $nav.css('overflow', 'visible'); 12110 } 12111 12112 /* Clone all of the entries into the More menu. 12113 * 12114 * We'll "move" items between the main list and the More menu by 12115 * selectively hiding and showing them in the appropriate place. */ 12116 function clone_entries_to_more() { 12117 $entries.clone().appendTo($more_list); 12118 return $more_list.find('.deep-nav--entry'); 12119 } 12120 12121 /* Make entries visible in the right place. 12122 * 12123 * Keep as many visible in the main entries list as we can, but if some 12124 * won't fit then we hide them in the main list and show them in the 12125 * "More" list. If no main entries are visible, then put us in Singleton 12126 * mode, where the More menu takes the name of the current page. */ 12127 function update_entry_visibility() { 12128 /* First we loop through the entries, making them visible in the main 12129 * list if the width allows, or in the More list otherwise. */ 12130 var available_width = $contents.innerWidth() - $remainder.outerWidth(), 12131 last_visible = -1, 12132 total_width = 0; 12133 for (var i = 0; i < $entries.length; i++) { 12134 var $entry = $($entries[i]); 12135 total_width += $entry.outerWidth(); 12136 if (total_width < available_width) { 12137 $($entries[i]).addClass('visible'); 12138 $($dropdown_entries[i]).removeClass('visible'); 12139 last_visible = i; 12140 } else { 12141 $($entries[i]).removeClass('visible'); 12142 $($dropdown_entries[i]).addClass('visible'); 12143 } 12144 } 12145 12146 /* If we end up with a single element in the "More" menu, move an 12147 * extra one in there. The "More" menu looks weird with only one item. */ 12148 if (last_visible == $entries.length - 2) { 12149 $($entries[last_visible]).removeClass('visible'); 12150 $($dropdown_entries[last_visible]).addClass('visible'); 12151 last_visible--; 12152 } 12153 12154 /* If there are any hidden items, mark the "More" menu as visible */ 12155 if (last_visible == $entries.length - 1) { 12156 // -1 because 0-based array index 12157 $remainder.removeClass('visible'); 12158 } else { 12159 $remainder.addClass('visible'); 12160 } 12161 } 12162 12163 component_onready(); 12164 12165 }); 12166}); 12167/** 12168 * Content Card Text Highlight Animation 12169 * 12170 * Highlights text elements within content cards based on their proximity 12171 * to the viewport center. Uses GSAP SplitText to split text into lines 12172 * and highlights the N closest lines to center. 12173 * 12174 * Supports multiple content card instances on a single page, each identified 12175 * by their unique instanceID. 12176 * 12177 * Works with custom scroll containers (not just window/body scroll). 12178 */ 12179document.addEventListener('DOMContentLoaded', function() { 12180 // Find all content cards with highlight animation enabled 12181 const contentCards = document.querySelectorAll('.wrap-content-card[data-enable-highlight-animation="true"]'); 12182 12183 if (contentCards.length === 0) { 12184 return; 12185 } 12186 12187 // Check if GSAP and SplitText are available 12188 if (typeof gsap === 'undefined') { 12189 console.warn('Highlight Animation: GSAP is not loaded'); 12190 return; 12191 } 12192 12193 if (typeof SplitText === 'undefined') { 12194 console.warn('Highlight Animation: SplitText plugin is not loaded'); 12195 return; 12196 } 12197 12198 // Register SplitText plugin 12199 gsap.registerPlugin(SplitText); 12200 12201 // Store all highlight instances keyed by instanceID 12202 const highlightInstances = {}; 12203 12204 // Store scroll containers to attach listeners 12205 const scrollContainers = new Set(); 12206 12207 // Desktop breakpoint - text scaling only applies at this width and above 12208 const DESKTOP_BREAKPOINT = 992; // Matches @screen-md-min from variables.less 12209 12210 /** 12211 * Check if viewport is at desktop breakpoint or above 12212 */ 12213 const isDesktopBreakpoint = function() { 12214 return window.innerWidth >= DESKTOP_BREAKPOINT; 12215 }; 12216 12217 /** 12218 * Find the scrolling container for an element 12219 * Traverses up the DOM to find the element with overflow: scroll/auto 12220 */ 12221 const findScrollContainer = function(element) { 12222 let parent = element.parentElement; 12223 12224 while (parent && parent !== document.body && parent !== document.documentElement) { 12225 const style = window.getComputedStyle(parent); 12226 const overflowY = style.overflowY; 12227 const overflowX = style.overflowX; 12228 12229 if (overflowY === 'scroll' || overflowY === 'auto' || 12230 overflowX === 'scroll' || overflowX === 'auto') { 12231 // Check if it actually has scrollable content 12232 if (parent.scrollHeight > parent.clientHeight || parent.scrollWidth > parent.clientWidth) { 12233 return parent; 12234 } 12235 } 12236 parent = parent.parentElement; 12237 } 12238 12239 // Default to document/window 12240 return null; 12241 }; 12242 12243 // Initialize each content card 12244 contentCards.forEach(function(card, index) { 12245 // Get unique instance ID from the card's id attribute 12246 const instanceId = card.id || 'highlight-instance-' + index; 12247 12248 const config = { 12249 instanceId: instanceId, 12250 numberOfLines: parseInt(card.getAttribute('data-number-of-lines-highlighted')) || 2, 12251 highlightColor: card.getAttribute('data-highlighted-text-color') || '#FFFFFF', // Default to white 12252 highlightScale: card.getAttribute('data-highlighted-text-scale') || null, 12253 borderLeftColor: card.getAttribute('data-highlighted-border-left-color') || null, 12254 borderRightColor: card.getAttribute('data-highlighted-border-right-color') || null, 12255 borderLeftPadding: card.getAttribute('data-highlighted-border-left-padding') || '0px', 12256 borderRightPadding: card.getAttribute('data-highlighted-border-right-padding') || '0px' 12257 }; 12258 12259 // Find the content container within this specific card instance 12260 const contentContainer = card.querySelector('.content-card--content'); 12261 if (!contentContainer) { 12262 console.warn('Highlight Animation: No content container found for card', instanceId); 12263 return; 12264 } 12265 12266 // Find the scroll container for this card 12267 const scrollContainer = findScrollContainer(card); 12268 if (scrollContainer) { 12269 scrollContainers.add(scrollContainer); 12270 } 12271 12272 // Find all text elements (headings and paragraphs) within this instance only 12273 const textElements = contentContainer.querySelectorAll('h1, h2, h3, h4, h5, h6, p'); 12274 if (textElements.length === 0) { 12275 console.warn('Highlight Animation: No text elements found for card', instanceId); 12276 return; 12277 } 12278 12279 // Store data for each text element 12280 const elementsData = []; 12281
12282 textElements.forEach(function(element, elemIndex) { 12283 // Store original styles 12284 const computedStyle = window.getComputedStyle(element); 12285 const originalColor = computedStyle.color; 12286 const textAlign = computedStyle.textAlign; 12287 12288 // Split text into lines with instance-specific class 12289 const split = new SplitText(element, { 12290 type: 'lines', 12291 linesClass: 'highlight-line highlight-line--' + instanceId.replace(/[^a-zA-Z0-9-_]/g, '-') 12292 }); 12293 12294 // Determine transform origin based on text alignment 12295 // Left-aligned text scales to the right, right-aligned text scales to the left 12296 let transformOrigin = 'left center'; // default for left/start alignment 12297 if (textAlign === 'right' || textAlign === 'end') { 12298 transformOrigin = 'right center'; 12299 } else if (textAlign === 'center') { 12300 transformOrigin = 'center center'; 12301 } 12302 12303 // Store data for each line 12304 const linesData = split.lines.map(function(line, lineIndex) { 12305 // Add data attribute for debugging 12306 line.setAttribute('data-highlight-instance', instanceId); 12307 line.setAttribute('data-highlight-line-index', lineIndex); 12308 12309 // Set transition on line (color, transform, borders, and margins) 12310 line.style.transition = 'color 0.6s ease, transform 0.6s ease, border-left-color 0.6s ease, border-right-color 0.6s ease, padding-left 0.6s ease, padding-right 0.6s ease, margin-top 0.6s ease, margin-bottom 0.6s ease'; 12311 line.style.transformOrigin = transformOrigin; 12312 line.style.display = 'block'; // Block display prevents horizontal overlap 12313 line.style.whiteSpace = 'nowrap'; // Prevents text from breaking within a line 12314 12315 return { 12316 element: line, 12317 originalColor: originalColor, 12318 transformOrigin: transformOrigin, 12319 isHighlighted: false 12320 }; 12321 }); 12322 12323 elementsData.push({ 12324 element: element, 12325 split: split, 12326 lines: linesData, 12327 originalColor: originalColor, 12328 textAlign: textAlign 12329 }); 12330 }); 12331 12332 // Store instance data keyed by instanceId 12333 const instance = { 12334 instanceId: instanceId, 12335 card: card, 12336 config: config, 12337 contentContainer: contentContainer, 12338 elementsData: elementsData 12339 }; 12340 12341 highlightInstances[instanceId] = instance; 12342 }); 12343 12344 if (Object.keys(highlightInstances).length === 0) { 12345 return; 12346 } 12347 12348 /** 12349 * Get the vertical center of the viewport 12350 * Uses viewport height directly since getBoundingClientRect is viewport-relative 12351 */ 12352 const getViewportCenterY = function() { 12353 return window.innerHeight / 2; 12354 }; 12355 12356 /** 12357 * Get the vertical center of an element relative to viewport 12358 */ 12359 const getElementCenterY = function(element) { 12360 const rect = element.getBoundingClientRect(); 12361 return rect.top + (rect.height / 2); 12362 }; 12363 12364 /** 12365 * Calculate distance from element center to viewport center 12366 */ 12367 const getDistanceToCenter = function(element) { 12368 const viewportCenterY = getViewportCenterY(); 12369 const elementCenterY = getElementCenterY(element); 12370 return Math.abs(elementCenterY - viewportCenterY); 12371 }; 12372 12373 /** 12374 * Update highlights for a single instance 12375 */ 12376 const updateHighlights = function(instance) { 12377 const config = instance.config; 12378 12379 // Maximum distance from center to be considered for highlighting 12380 // Lines further than this won't highlight even if they're the closest 12381 const maxDistanceThreshold = window.innerHeight * 0.3; // 30% of viewport height 12382 12383 // Collect all lines with their distances to center 12384 const allLines = []; 12385 12386 instance.elementsData.forEach(function(elementData) {
12387 elementData.lines.forEach(function(lineData) { 12388 const distance = getDistanceToCenter(lineData.element); 12389 allLines.push({ 12390 lineData: lineData, 12391 distance: distance, 12392 parentElement: elementData.element 12393 }); 12394 }); 12395 }); 12396 12397 // Sort by distance to center 12398 allLines.sort(function(a, b) { 12399 return a.distance - b.distance; 12400 }); 12401 12402 // Filter lines that are within the threshold (close enough to viewport center) 12403 const linesWithinThreshold = allLines.filter(function(item) { 12404 return item.distance <= maxDistanceThreshold; 12405 }); 12406 12407 // Get the lines to highlight (N closest to center, but only from those within threshold) 12408 const linesToHighlight = linesWithinThreshold.slice(0, config.numberOfLines); 12409 const linesToHighlightSet = new Set(linesToHighlight.map(function(item) { 12410 return item.lineData; 12411 })); 12412 12413 // Update all lines 12414 allLines.forEach(function(item) { 12415 const lineData = item.lineData; 12416 const shouldHighlight = linesToHighlightSet.has(lineData); 12417 12418 if (shouldHighlight && !lineData.isHighlighted) { 12419 // Apply highlight 12420 lineData.element.style.color = config.highlightColor; 12421 // Only apply text scaling at desktop breakpoint and above 12422 if (config.highlightScale && isDesktopBreakpoint()) { 12423 lineData.element.style.transform = 'scale(' + config.highlightScale + ')'; 12424 // Add margin to compensate for visual scale increase and prevent overlap 12425 var scaleMargin = ((parseFloat(config.highlightScale) - 1) * 0.5) + 'em'; 12426 lineData.element.style.marginTop = scaleMargin; 12427 lineData.element.style.marginBottom = scaleMargin; 12428 } 12429 // Apply border colors if set 12430 if (config.borderLeftColor) { 12431 lineData.element.style.borderLeft = '3px solid ' + config.borderLeftColor; 12432 lineData.element.style.paddingLeft = config.borderLeftPadding; 12433 } 12434 if (config.borderRightColor) { 12435 lineData.element.style.borderRight = '3px solid ' + config.borderRightColor; 12436 lineData.element.style.paddingRight = config.borderRightPadding; 12437 } 12438 lineData.isHighlighted = true; 12439 } else if (!shouldHighlight && lineData.isHighlighted) { 12440 // Remove highlight 12441 lineData.element.style.color = lineData.originalColor; 12442 if (config.highlightScale) { 12443 lineData.element.style.transform = 'scale(1)'; 12444 // Remove margin compensation 12445 lineData.element.style.marginTop = '0'; 12446 lineData.element.style.marginBottom = '0'; 12447 } 12448 // Remove borders 12449 if (config.borderLeftColor) { 12450 lineData.element.style.borderLeft = '0px solid transparent'; 12451 lineData.element.style.paddingLeft = '0px'; 12452 } 12453 if (config.borderRightColor) { 12454 lineData.element.style.borderRight = '0px solid transparent'; 12455 lineData.element.style.paddingRight = '0px'; 12456 } 12457 lineData.isHighlighted = false; 12458 } 12459 }); 12460 }; 12461 12462 /** 12463 * Update all highlight instances 12464 */ 12465 const updateAllHighlights = function() { 12466 Object.values(highlightInstances).forEach(updateHighlights); 12467 }; 12468 12469 // Initial update 12470 updateAllHighlights(); 12471 12472 // Update on scroll with throttling using requestAnimationFrame 12473 let isScrolling = false; 12474 12475 const handleScroll = function() { 12476 if (!isScrolling) { 12477 isScrolling = true; 12478 requestAnimationFrame(function() { 12479 updateAllHighlights(); 12480 isScrolling = false;
12481 }); 12482 } 12483 }; 12484 12485 // Attach scroll listener to window 12486 window.addEventListener('scroll', handleScroll, { passive: true }); 12487 12488 // Attach scroll listener to document (for some edge cases) 12489 document.addEventListener('scroll', handleScroll, { passive: true }); 12490 12491 // Attach scroll listener to all detected scroll containers 12492 scrollContainers.forEach(function(container) { 12493 container.addEventListener('scroll', handleScroll, { passive: true }); 12494 }); 12495 12496 // Update on resize (lines may reflow) 12497 let resizeTimeout; 12498 let isResizing = false; 12499 12500 window.addEventListener('resize', function() { 12501 clearTimeout(resizeTimeout); 12502 isResizing = true; 12503 12504 // Use longer debounce (500ms) to ensure resize has truly stopped 12505 resizeTimeout = setTimeout(function() { 12506 // Re-split text on resize as lines may have changed 12507 Object.values(highlightInstances).forEach(function(instance) { 12508 const instanceId = instance.instanceId; 12509 const contentContainer = instance.contentContainer; 12510 12511 // Step 1: Fade out content container to hide the jarring transition 12512 contentContainer.style.transition = 'opacity 0.2s ease'; 12513 contentContainer.style.opacity = '0'; 12514 12515 // Step 2: After fade out, perform the re-split 12516 setTimeout(function() { 12517 instance.elementsData.forEach(function(elementData) { 12518 // Revert split 12519 elementData.split.revert(); 12520 12521 // Re-split with instance-specific class 12522 elementData.split = new SplitText(elementData.element, { 12523 type: 'lines', 12524 linesClass: 'highlight-line highlight-line--' + instanceId.replace(/[^a-zA-Z0-9-_]/g, '-') 12525 }); 12526 12527 // Determine transform origin based on text alignment 12528 let transformOrigin = 'left center'; 12529 if (elementData.textAlign === 'right' || elementData.textAlign === 'end') { 12530 transformOrigin = 'right center'; 12531 } else if (elementData.textAlign === 'center') { 12532 transformOrigin = 'center center'; 12533 } 12534 12535 // Update lines data with transitions disabled initially 12536 elementData.lines = elementData.split.lines.map(function(line, lineIndex) { 12537 // Add data attribute for debugging 12538 line.setAttribute('data-highlight-instance', instanceId); 12539 line.setAttribute('data-highlight-line-index', lineIndex); 12540 12541 // Disable transitions initially to prevent animation artifacts 12542 line.style.transition = 'none'; 12543 line.style.transformOrigin = transformOrigin; 12544 line.style.display = 'block'; // Block display prevents horizontal overlap 12545 line.style.whiteSpace = 'nowrap'; // Prevents text from breaking within a line 12546 12547 return { 12548 element: line, 12549 originalColor: elementData.originalColor, 12550 transformOrigin: transformOrigin, 12551 isHighlighted: false 12552 }; 12553 }); 12554 }); 12555 12556 // Update highlights immediately (without transitions) 12557 updateHighlights(instance); 12558 12559 // Step 3: Re-enable transitions after a brief delay 12560 setTimeout(function() { 12561 instance.elementsData.forEach(function(elementData) {
12562 elementData.lines.forEach(function(lineData) { 12563 // Re-enable transitions 12564 lineData.element.style.transition = 'color 0.6s ease, transform 0.6s ease, border-left-color 0.6s ease, border-right-color 0.6s ease, padding-left 0.6s ease, padding-right 0.6s ease, margin-top 0.6s ease, margin-bottom 0.6s ease'; 12565 }); 12566 }); 12567 12568 // Step 4: Fade content back in 12569 contentContainer.style.opacity = '1'; 12570 12571 // Reset transition after fade in completes 12572 setTimeout(function() { 12573 contentContainer.style.transition = ''; 12574 }, 200); 12575 12576 isResizing = false; 12577 }, 50); 12578 }, 200); // Wait for fade out to complete 12579 }); 12580 }, 500); // 500ms debounce to ensure resize has stopped 12581 }); 12582 12583}); 12584 12585$(function(){ 12586 $(document).ready(function() { 12587 var phonenumber = $('.js-phone').text().replace(/[^\w\s]/gi, '').replace(' ', ''); 12588 if(phonenumber.length == 10) 12589 { 12590 $('.js-phone').attr('href', 'tel:+1'+phonenumber); 12591 } 12592 else { 12593 $('.js-phone').attr('href', 'tel:+'+phonenumber); 12594 } 12595 }); 12596}); 12597 12598(function () { 12599 12600 function hideEmptyColumnsOnMobile() { 12601 $('.row section > div').each(function () { 12602 if ($(this).children().length === 0) { 12603 var parent = $(this).parent() 12604 12605 if (!$(parent).is(':last-child')) { 12606 if ($(window).width() <= 750) { 12607 parent.hide(); 12608 } 12609 else { 12610 parent.show(); 12611 12612 } 12613 } 12614 else { 12615 parent.css("padding-top", 0); 12616 } 12617 } 12618 12619 }) 12620 } 12621 12622 $(document).ready(function () { 12623 hideEmptyColumnsOnMobile(); 12624 clearTimeout(resizeTimer); 12625 resizeTimer = setTimeout(hideEmptyColumnsOnMobile(), 250); 12626 if($('.parallax').length > 0 && navigator.userAgent.match(/Trident\/7\./)) 12627 { 12628 $('body').on("mousewheel", function () { 12629 // remove default behavior 12630 event.preventDefault(); 12631 12632 //scroll without smoothing 12633 var wheelDelta = event.wheelDelta; 12634 var currentScrollPosition = window.pageYOffset; 12635 window.scrollTo(0, currentScrollPosition - wheelDelta); 12636 }); 12637 } 12638 12639 }) 12640 12641 var resizeTimer; 12642 $(window).resize(function (e) { 12643 hideEmptyColumnsOnMobile() 12644 12645 }); 12646 12647 12648}()); 12649(function () { 12650 if( $('.callout-slider').length > 0 ) { 12651 var bindToClass = 'wrap-callout-slider ', 12652 containerWidth = 0, 12653 scrollWidth = 0, 12654 posFromLeft = 0, // Stripe position from the left of the screen 12655 stripePos = 0, // When relative mouse position inside the thumbs stripe 12656 animated = null, 12657 $carousel, el, $el, ratio, scrollPos, nextMore, prevMore, pos, padding; 12658 12659 12660 $.fn.carousel = function(options){ 12661 $(document) 12662 .on('mouseenter.carousel', '.' + bindToClass, calc) 12663 .on('mousemove.carousel', '.' + bindToClass, move); 12664 }; 12665 // automatic binding to all elements which have the class that is assigned to "bindToClass" 12666 $.fn.carousel(); 12667 } 12668 // calculate the thumbs container width 12669 function calc(e){ 12670 $el = $(this).find('.callout-slider'); 12671 el = $el[0]; 12672 $carousel = $el.parent(); 12673 12674 nextMore = prevMore = false; // reset 12675 12676 containerWidth = el.clientWidth; 12677 scrollWidth = el.scrollWidth; // the "<ul>"" width 12678 padding = 0.2 * containerWidth; // padding in percentage of the area which the mouse movement affects 12679 posFromLeft = $el.offset().left; 12680 stripePos = e.pageX - padding - posFromLeft; 12681 pos = stripePos / (containerWidth - padding*2); 12682 scrollPos = (scrollWidth - containerWidth ) * pos; 12683 12684 if( scrollPos < 0 ) 12685 scrollPos = 0; 12686 if( scrollPos > (scrollWidth - containerWidth) ) 12687 scrollPos = scrollWidth - containerWidth; 12688 12689 $el.animate({scrollLeft:scrollPos}, 200, 'swing'); 12690 12691 clearTimeout(animated); 12692 animated = setTimeout(function(){ 12693 animated = null; 12694 }, 200); 12695 12696 return this; 12697 } 12698 // move the stripe left or right according to mouse position 12699 function move(e){ 12700 // don't move anything until inital movement on 'mouseenter' has finished 12701 if( animated ) return; 12702 12703 ratio = scrollWidth / containerWidth; 12704 stripePos = e.pageX - padding - posFromLeft; // the mouse X position, "normalized" to the carousel position 12705 12706 if( stripePos < 0) 12707 stripePos = 0; 12708 12709 pos = stripePos / (containerWidth - padding*2); // calculated position between 0 to 1 12710 // calculate the percentage of the mouse position within the carousel
12711 scrollPos = (scrollWidth - containerWidth ) * pos; 12712 12713 el.scrollLeft = scrollPos; 12714 12715 12716 // check if element has reached an edge 12717 prevMore = el.scrollLeft > 0; 12718 nextMore = el.scrollLeft < (scrollWidth - containerWidth); 12719 12720 $carousel.toggleClass('left', prevMore); 12721 $carousel.toggleClass('right', nextMore); 12722 } 12723 12724}()); 12725$(function () { 12726 $numberOfPeople = $('#numberOfPeople'); 12727 $grossMonthlyIncome = $('#grossMonthlyIncome'); 12728 $county = $('#county'); 12729 12730 var titles = []; 12731 12732 var url = new URL(window.location.href); 12733 var c3PathValue = url.searchParams.get('c3path'); 12734 var c3Titles = c3PathValue ? c3PathValue.split('|') : []; 12735 if (c3Titles.length > 0) { 12736 $('.eligible-programs__result').hide(); 12737 c3Titles.forEach(function (val) { 12738 $('.eligible-programs__result[data-name="' + val + '"]').show(); 12739 }); 12740 showScreen(4); 12741 } 12742 function getShareUrl() { 12743 values = ''; 12744 titles.forEach(function (title) { 12745 values += title + '|'; 12746 }); 12747 12748 return values; 12749 } 12750 12751 $('.email-button').click(function () { 12752 $('.email-submit--background').show(); 12753 $('.email-submit').show(); 12754 }); 12755 12756 $('.email__close').click(function () { 12757 $('.email-submit--background').hide(); 12758 $('.email-submit').hide(); 12759 }); 12760 12761 function getErrorMessage(that) { 12762 if ($(that).closest('.c3-form').hasClass('.c3-form--spanish')) { 12763 return 'This field is required.'; 12764 } else { 12765 return 'This field is required.'; 12766 } 12767 } 12768 12769 $('.send-email-js').click(function () { 12770 var email = $('.email--input').val(); 12771 var path = $(this).data('path'); 12772 resetMessage(); 12773 if (!email) { 12774 $(this).addClass('input--error'); 12775 $(this) 12776 .prev() 12777 .before( 12778 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12779 ); 12780 } else { 12781 $.ajax({ 12782 type: 'POST', 12783 url: '/bin/southern/global/c3email', 12784 data: { 12785 email: email, 12786 path: path, 12787 url: window.location.href, 12788 c3: getShareUrl(), 12789 }, 12790 success: function (msg) { 12791 $('.email-submit--background').hide(); 12792 $('.email-submit').hide(); 12793 }, 12794 error: function () { 12795 $(this).addClass('input--error'); 12796 $(this) 12797 .prev() 12798 .before( 12799 "<p class='input-message--error'>There was an issue sending the email. Please try again later.</p>" 12800 ); 12801 }, 12802 }); 12803 } 12804 }); 12805 12806 function resetMessage() { 12807 $('.input-message--error').remove(); 12808 $('.input--error').removeClass('input--error'); 12809 } 12810 $('.js-screen-1__button').click(function () { 12811 //valid 12812 12813 resetMessage(); 12814 var valid = true; 12815 if ($numberOfPeople.val() === 'Select') { 12816 $numberOfPeople.addClass('input--error'); 12817 $numberOfPeople 12818 .prev() 12819 .before( 12820 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12821 ); 12822 valid = false; 12823 } 12824 if (!$grossMonthlyIncome.val()) { 12825 $grossMonthlyIncome.addClass('input--error'); 12826 $grossMonthlyIncome 12827 .prev() 12828 .before( 12829 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12830 ); 12831 valid = false; 12832 } 12833 if ($county.val() === 'Select') { 12834 $county.addClass('input--error'); 12835 $county 12836 .prev() 12837 .before( 12838 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12839 ); 12840 valid = false; 12841 } 12842 if (valid) { 12843 adjustBarForward(2); 12844 showScreen(2); 12845 } 12846 }); 12847 12848 $('.js-screen-2__button').click(function () { 12849 //valid 12850 var valid = true; 12851 resetMessage(); 12852 if (!$('.assistance').is(':checked')) { 12853 $('.assistance__headline').before( 12854 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12855 ); 12856 valid = false; 12857 } 12858 if (valid) { 12859 adjustBarForward(3); 12860 showScreen(3); 12861 } 12862 }); 12863 12864 $('.js-screen-3__button').click(function () { 12865 //valid 12866 var valid = true; 12867 resetMessage(); 12868 hideStatus(); 12869 12870 if (!$('.trouble').is(':checked')) { 12871 $('.trouble__headline').before( 12872 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12873 ); 12874 valid = false;
12875 } 12876 if (!$('.military').is(':checked')) { 12877 $('.military__headline').before( 12878 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12879 ); 12880 valid = false; 12881 } 12882 if (!$('.situation').is(':checked')) { 12883 $('.situation__headline').before( 12884 "<p class='input-message--error'>" + getErrorMessage(this) + '</p>' 12885 ); 12886 valid = false; 12887 } 12888 if (valid) { 12889 titles = getTitlesToShow(); 12890 12891 url = getShareUrl(); 12892 $('.eligible-programs__result').hide(); 12893 titles.forEach(function (val) { 12894 $('.eligible-programs__result[data-name="' + val + '"]').show(); 12895 }); 12896 showScreen(4); 12897 } 12898 }); 12899 12900 $('#troublePayingYes').change(function () { 12901 if ($(this).is(':checked')) { 12902 $('.row--30-trouble').show(); 12903 } 12904 }); 12905 12906 $('#troublePayingNo').change(function () { 12907 if ($(this).is(':checked')) { 12908 $('.row--30-trouble').hide(); 12909 } 12910 }); 12911 12912 $('.js-screen').click(function () { 12913 //valid 12914 var screen = $(this).data('screen'); 12915 showScreen(screen); 12916 adjustBarBack(screen); 12917 }); 12918 12919 function showScreen(screenNumber) { 12920 $('.c3-screen').hide(); 12921 $('.c3-screen[data-screen="' + screenNumber + '"]').show(); 12922 } 12923 12924 function getValues() { 12925 var vals = []; 12926 vals['numberOfPeople'] = $numberOfPeople.val(); 12927 vals['grossMonthlyIncome'] = $grossMonthlyIncome.val(); 12928 vals['county'] = $county.val(); 12929 $('.assistance').each(function () { 12930 var nameVal = $(this).attr('name'); 12931 var value = $(this).is(':checked'); 12932 vals[nameVal] = value; 12933 }); 12934 vals['troublePaying'] = $('input[name=troublePaying]:checked').val(); 12935 vals['troublePaying30'] = $('input[name=troublePaying30]:checked').val(); 12936 vals['military'] = $('input[name=military]:checked').val(); 12937 $('.situation').each(function () { 12938 var nameVal = $(this).attr('name'); 12939 var value = $(this).is(':checked'); 12940 vals[nameVal] = value; 12941 }); 12942 12943 return vals; 12944 } 12945 12946 function getTitlesToShow() { 12947 var data = getValues(); 12948 var titles = ['IE Energy Efficiency Recommendation', 'On-Bill Financing']; 12949 if (qualifyForLIHEAP(data)) { 12950 titles.push('LIHEAP'); 12951 } 12952 if (qualifyForSharingProgram(data)) { 12953 titles.push('Sharing Program'); 12954 } 12955 if (qualifyForSharingProgramMilitary(data)) { 12956 titles.push('Sharing Program (Military)'); 12957 } 12958 if (qualifyForEnergyAideProgram(data)) { 12959 titles.push('Energy Aide Program'); 12960 } 12961 if (qualifyForShieldOfCaring(data)) { 12962 titles.push('Shield of Caring'); 12963 } 12964 if (incomeAndCountyQualify(data, ['Adams', 'Pike'])) { 12965 titles.push('Two Rivers Regional Council of Public Officials'); 12966 } 12967 if (incomeAndCountyQualify(data, ['Boone'])) { 12968 titles.push('Rockford Human Services Department'); 12969 } 12970 if ( 12971 incomeAndCountyQualify(data, [ 12972 'Bureau', 12973 'Carroll', 12974 'LaSalle', 12975 'Lee', 12976 'Ogle', 12977 'Whiteside', 12978 ]) 12979 ) { 12980 titles.push('Tri-County Opportunities Council'); 12981 } 12982 if (incomeAndCountyQualify(data, ['Champaign'])) { 12983 titles.push('Champaign County Regional Planning Commission'); 12984 } 12985 if (incomeAndCountyQualify(data, ['DeKalb'])) { 12986 titles.push('Community Contacts (DeKalb County)'); 12987 } 12988 if (incomeAndCountyQualify(data, ['Cook'])) { 12989 titles.push('Community and Economic Development Association'); 12990 } 12991 if (incomeAndCountyQualify(data, ['DeWitt', 'Clinton', 'Edgar'])) { 12992 titles.push('Community Action Partnership of Central Illinois'); 12993 } 12994 if (incomeAndCountyQualify(data, ['DuPage'])) { 12995 titles.push('DuPage County Department of Human Services'); 12996 } 12997 if (incomeAndCountyQualify(data, ['Ford', 'Iroquois', 'Vermillion'])) { 12998 titles.push('East Central Illinois Community Action Agency'); 12999 } 13000 if (incomeAndCountyQualify(data, ['Grundy', 'Kendall'])) { 13001 titles.push('Kendall-Grundy Community Action'); 13002 } 13003 if (incomeAndCountyQualify(data, ['Hancock', 'Henderson'])) { 13004 titles.push('Western Illinois Regional Council'); 13005 } 13006 if (incomeAndCountyQualify(data, ['Henry'])) { 13007 titles.push('Project NOW, Inc'); 13008 } 13009 if (incomeAndCountyQualify(data, ['Jo Daviess', 'Stephenson'])) { 13010 titles.push('Northwestern Illinois Community Action Agency'); 13011 } 13012 if (incomeAndCountyQualify(data, ['Kane'])) { 13013 titles.push('Community Contacts (Kane County)'); 13014 } 13015 if (incomeAndCountyQualify(data, ['Kankakee'])) { 13016 titles.push('Kankakee County Community Services'); 13017 } 13018 if (incomeAndCountyQualify(data, ['Lake'])) { 13019 titles.push('Community Action Partnership of Lake County'); 13020 } 13021 if (incomeAndCountyQualify(data, ['Winnebago'])) { 13022 titles.push('Rockford Human Service Department'); 13023 } 13024 if (incomeAndCountyQualify(data, ['Livingston', 'McLean'])) { 13025 titles.push('Mid Central Community Action, Inc.'); 13026 } 13027 if (incomeAndCountyQualify(data, ['McHenry'])) { 13028 titles.push('McHenry County Housing Authority'); 13029 } 13030 if (incomeAndCountyQualify(data, ['Tazewell', 'Woodford'])) { 13031 titles.push('Tazwood Community Services, Inc'); 13032 } 13033 if (incomeAndCountyQualify(data, ['Will'])) { 13034 titles.push('Will County Center for Community Concerns'); 13035 } 13036 if (unitedWayAndSalvationArmyQualify(data)) { 13037 titles.push('United Way'); 13038 titles.push('Salvation Army'); 13039 } 13040 if (marketrateEnergyEfficiencyQualify(data)) { 13041 titles.push('Marketrate Energy Efficiency'); 13042 } 13043 if (qualifyBudgetBill(data)) { 13044 titles.push('Budget Bill'); 13045 }
13046 if (qualifyDeferredPaymentArrangement(data)) { 13047 titles.push('Deferred Payment Arrangement'); 13048 } 13049 13050 return titles; 13051 } 13052 13053 function qualifyBudgetBill(data) { 13054 var trouble = data['troublePaying'] === 'Yes'; 13055 return trouble; 13056 } 13057 13058 function qualifyDeferredPaymentArrangement(data) { 13059 var trouble = data['troublePaying'] === 'Yes'; 13060 var trouble30 = data['troublePaying30'] === 'Yes'; 13061 return trouble && trouble30; 13062 } 13063 13064 function qualifyForLIHEAP(data) { 13065 var numberOfPeople = data['numberOfPeople']; 13066 var income = parseFloat(data['grossMonthlyIncome']); 13067 var pipp = data['Percentage of Income Payment Plan (PIPP)']; 13068 var liheap = data['Low Income Home Energy Assistance Program (LIHEAP)']; 13069 var trouble = data['troublePaying'] === 'Yes'; 13070 13071 if (!liheap && !pipp && trouble) { 13072 if (numberOfPeople === '1' && income <= 2510) { 13073 return true; 13074 } else if (numberOfPeople === '2' && income <= 3407) { 13075 return true; 13076 } else if (numberOfPeople === '3' && income <= 4303) { 13077 return true; 13078 } else if (numberOfPeople === '4' && income <= 5200) { 13079 return true; 13080 } else if (numberOfPeople === '5' && income <= 6097) { 13081 return true; 13082 } else if (numberOfPeople === '6' && income <= 6993) { 13083 return true; 13084 } else if (numberOfPeople === '7' && income <= 7890) { 13085 return true; 13086 } else if (numberOfPeople === '8' && income <= 8427) { 13087 return true; 13088 } else if (numberOfPeople === '9' && income <= 8611) { 13089 return true; 13090 } else if (numberOfPeople === '10+' && income <= 8794) { 13091 return true; 13092 } 13093 } 13094 return false; 13095} 13096 13097 13098function qualifyForSharingProgram(data) { 13099 var trouble = data['troublePaying'] === 'Yes'; 13100 var numberOfPeople = data['numberOfPeople']; 13101 var income = parseFloat(data['grossMonthlyIncome']); 13102 13103 if (trouble) { 13104 if (numberOfPeople === '1' && income > 2510 && income <= 3765) { 13105 return true; 13106 } else if (numberOfPeople === '2' && income > 3407 && income <= 5110) { 13107 return true; 13108 } else if (numberOfPeople === '3' && income > 4303 && income <= 6455) { 13109 return true; 13110 } else if (numberOfPeople === '4' && income > 5200 && income <= 7800) { 13111 return true; 13112 } else if (numberOfPeople === '5' && income > 6097 && income <= 9145) { 13113 return true; 13114 } else if (numberOfPeople === '6' && income > 6993 && income <= 10490) { 13115 return true; 13116 } else if (numberOfPeople === '7' && income > 7890 && income <= 11835) { 13117 return true; 13118 } else if (numberOfPeople === '8' && income > 8427 && income <= 13180) { 13119 return true; 13120 } else if (numberOfPeople === '9' && income > 8611 && income <= 14525) { 13121 return true; 13122 } else if (numberOfPeople === '10+' && income > 8794 && income <= 15870) { 13123 return true; 13124 } 13125 } 13126 return false; 13127} 13128function qualifyForSharingProgramMilitary(data) { 13129 var trouble = data['troublePaying'] === 'Yes'; 13130 var military = data['military'] === 'Yes'; 13131 var numberOfPeople = data['numberOfPeople']; 13132 var income = parseFloat(data['grossMonthlyIncome']); 13133 13134 if (trouble && military) { 13135 if (numberOfPeople === '1' && income < 3765) { 13136 return true; 13137 } else if (numberOfPeople === '2' && income < 5110) { 13138 return true; 13139 } else if (numberOfPeople === '3' && income < 6455) { 13140 return true; 13141 } else if (numberOfPeople === '4' && income < 7800) { 13142 return true; 13143 } else if (numberOfPeople === '5' && income < 9145) { 13144 return true; 13145 } else if (numberOfPeople === '6' && income < 10490) { 13146 return true; 13147 } else if (numberOfPeople === '7' && income < 11835) { 13148 return true; 13149 } else if (numberOfPeople === '8' && income < 13180) { 13150 return true; 13151 } else if (numberOfPeople === '9' && income < 14525) { 13152 return true; 13153 } else if (numberOfPeople === '10+' && income < 15870) { 13154 return true; 13155 } 13156 } 13157 return false;
13158} 13159function qualifyForEnergyAideProgram(data) { 13160 var trouble = data['troublePaying'] === 'Yes'; 13161 var numberOfPeople = data['numberOfPeople']; 13162 var income = parseFloat(data['grossMonthlyIncome']); 13163 var trouble30 = data['troublePaying30'] === 'Yes'; 13164 var county = data['county']; 13165 var countyIsValid = county !== 'Kankakee' && county !== 'Kane' && county !== 'DeKalb'; 13166 13167 if (trouble && trouble30 && countyIsValid) { 13168 if (numberOfPeople === '1' && income <= 4393) { 13169 return true; 13170 } else if (numberOfPeople === '2' && income <= 5962) { 13171 return true; 13172 } else if (numberOfPeople === '3' && income <= 7531) { 13173 return true; 13174 } else if (numberOfPeople === '4' && income <= 9100) { 13175 return true; 13176 } else if (numberOfPeople === '5' && income <= 10669) { 13177 return true; 13178 } else if (numberOfPeople === '6' && income <= 12238) { 13179 return true; 13180 } else if (numberOfPeople === '7' && income <= 13808) { 13181 return true; 13182 } else if (numberOfPeople === '8' && income <= 15377) { 13183 return true; 13184 } else if (numberOfPeople === '9' && income <= 16946) { 13185 return true; 13186 } else if (numberOfPeople === '10+' && income <= 18515) { 13187 return true; 13188 } 13189 } 13190 return false; 13191} 13192 13193 13194function qualifyForShieldOfCaring(data) { 13195 var trouble = data['troublePaying'] === 'Yes'; 13196 var trouble30 = data['troublePaying30'] === 'Yes'; 13197 var numberOfPeople = data['numberOfPeople']; 13198 var income = parseFloat(data['grossMonthlyIncome']); 13199 13200 if (trouble && trouble30) { 13201 if (numberOfPeople === '1' && income <= 5020) { 13202 return true; 13203 } else if (numberOfPeople === '2' && income <= 6813) { 13204 return true; 13205 } else if (numberOfPeople === '3' && income <= 8607) { 13206 return true; 13207 } else if (numberOfPeople === '4' && income <= 10400) { 13208 return true; 13209 } else if (numberOfPeople === '5' && income <= 12193) { 13210 return true; 13211 } else if (numberOfPeople === '6' && income <= 13987) { 13212 return true; 13213 } else if (numberOfPeople === '7' && income <= 15780) { 13214 return true; 13215 } else if (numberOfPeople === '8' && income <= 17573) { 13216 return true; 13217 } else if (numberOfPeople === '9' && income <= 19367) { 13218 return true; 13219 } else if (numberOfPeople === '10+' && income <= 21160) { 13220 return true; 13221 } 13222 } 13223 return false; 13224} 13225 13226 13227function incomeAndCountyQualify(data, countiesArray) { 13228 var county = data['county']; 13229 var countyQualifies = countiesArray.includes(county); 13230 var numberOfPeople = data['numberOfPeople']; 13231 var income = parseFloat(data['grossMonthlyIncome']); 13232 var incomeQualify = false; 13233 13234 if (numberOfPeople === '1' && income < 2510) { 13235 incomeQualify = true; 13236 } else if (numberOfPeople === '2' && income < 3407) { 13237 incomeQualify = true; 13238 } else if (numberOfPeople === '3' && income < 4303) { 13239 incomeQualify = true; 13240 } else if (numberOfPeople === '4' && income < 5200) { 13241 incomeQualify = true; 13242 } else if (numberOfPeople === '5' && income < 6097) { 13243 incomeQualify = true; 13244 } else if (numberOfPeople === '6' && income < 6993) { 13245 incomeQualify = true; 13246 } else if (numberOfPeople === '7' && income < 7890) { 13247 incomeQualify = true; 13248 } else if (numberOfPeople === '8' && income < 8427) { 13249 incomeQualify = true; 13250 } else if (numberOfPeople === '9' && income < 8611) { 13251 incomeQualify = true; 13252 } else if (numberOfPeople === '10+' && income < 8794) { 13253 incomeQualify = true; 13254 } 13255 13256 return countyQualifies && incomeQualify; 13257} 13258 13259 13260 function unitedWayAndSalvationArmyQualify(data) { 13261 var familyCrisis = data['Family Crisis']; 13262 var lossOfEmployment = data['Loss of Employment']; 13263 var unexpectedOfIncome = data['Unexpected Lost of income']; 13264 var medicalHardship = data['Medical Hardship']; 13265 var seniorCitizen = data['Senior Citizen']; 13266 var disability = data['Disability']; 13267 return ( 13268 familyCrisis || 13269 lossOfEmployment || 13270 unexpectedOfIncome || 13271 medicalHardship || 13272 seniorCitizen || 13273 disability 13274 ); 13275 } 13276 function marketrateEnergyEfficiencyQualify(data) { 13277 var numberOfPeople = data['numberOfPeople']; 13278 var income = parseFloat(data['grossMonthlyIncome']); 13279 13280 if (numberOfPeople === '1' && income > 5020) { 13281 return true; 13282 } else if (numberOfPeople === '2' && income > 6813) { 13283 return true; 13284 } else if (numberOfPeople === '3' && income > 8607) { 13285 return true; 13286 } else if (numberOfPeople === '4' && income > 10400) { 13287 return true; 13288 } else if (numberOfPeople === '5' && income > 12193) { 13289 return true; 13290 } else if (numberOfPeople === '6' && income > 13987) { 13291 return true; 13292 } else if (numberOfPeople === '7' && income > 15780) { 13293 return true; 13294 } else if (numberOfPeople === '8' && income > 17573) { 13295 return true; 13296 } else if (numberOfPeople === '9' && income > 19367) { 13297 return true; 13298 } else if (numberOfPeople === '10+' && income > 21160) { 13299 return true; 13300 } 13301 13302 return false;
13303} 13304 13305 13306 function getMessage(that) { 13307 if ($(that).closest('.c3-form').hasClass('c3-form--spanish')) { 13308 return 'Must include all income received for persons 18 years and older. Gross income is total income before taxes.'; 13309 } else { 13310 return 'Debe incluir todos los ingresos recibidos por personas mayores de 18 años. El ingreso bruto es el ingreso total antes de impuestos.'; 13311 } 13312 } 13313 13314 var sections = $('.gross-monthly-income__container label'); 13315 13316 sections.each(function (i, element) { 13317 var text = getMessage(element); 13318 if ( 13319 element.outerText == 13320 'Must include all income received for persons 18 years and older. Gross income is total income before taxes.' || 13321 element.outerText == 13322 'Debe incluir todos los ingresos recibidos por personas mayores de 18 años. El ingreso bruto es el ingreso total antes de impuestos.' 13323 ) { 13324 $(element).addClass('custom-ss'); 13325 13326 var parent = $(element).parent()[0]; 13327 13328 $(parent).addClass('custom-image'); 13329 } 13330 }); 13331 13332 var imageContainer = $('.gross-monthly-income__container'); 13333 13334 var img = document.createElement('img'); 13335 13336 img.src = 13337 '/content/dam/global/images/icons/callout-icons/information-icon-6055.png'; 13338 13339 $(imageContainer[0]).append(img); 13340 13341 $('.gross-monthly-income__container img').on('click touch', function () { 13342 if ($(this).parent().hasClass('gross-monthly__image--hover')) { 13343 $(this).parent().removeClass('gross-monthly__image--hover'); 13344 } else { 13345 $(this).parent().addClass('gross-monthly__image--hover'); 13346 } 13347 }); 13348 13349 $('.gross-monthly-income__container img').on('mouseover', function () { 13350 $(this).parent().addClass('gross-monthly__image--hover'); 13351 13352 $(this).one('mouseleave', function () { 13353 $(this).parent().removeClass('gross-monthly__image--hover'); 13354 }); 13355 }); 13356}); 13357 13358 13359 13360function adjustBarForward(stepTo) { 13361 var lines = $(".c3-status").find(".spacer-line"); 13362 var steps = $(".c3-status").find(".number-container"); 13363 var tags = $(".c3-status").find(".number-container p"); 13364 $(steps[stepTo - 1]).addClass("active"); 13365 $(lines[stepTo - 2]).addClass("active"); 13366 $(steps[stepTo - 2]).addClass("complete"); 13367 $(tags[stepTo - 2]).addClass("checkmark"); 13368 13369} 13370 13371function adjustBarBack(stepTo) { 13372 var lines = $(".c3-status").find(".spacer-line"); 13373 var steps = $(".c3-status").find(".number-container"); 13374 var tags = $(".c3-status").find(".number-container p"); 13375 $(steps[stepTo]).removeClass("active"); 13376 $(lines[stepTo - 1]).removeClass("active"); 13377 $(steps[stepTo - 1]).removeClass("complete"); 13378 $(tags[stepTo - 1]).removeClass("checkmark"); 13379 $('.c3-status').removeClass("hide"); 13380} 13381 13382function hideStatus() { 13383 $('.c3-status').addClass("hide"); 13384} 13385let openCard = undefined; 13386 13387document 13388 .querySelectorAll('.business-solutions .expansion-button button') 13389 .forEach((button) => { 13390 13391 // Handle mouse enter (hover) 13392 button.addEventListener('mouseenter', (event) => { 13393 if (event.pointerType != 'mouse') { 13394 return; 13395 } 13396 if (event.currentTarget.dataset.state == "closed") { 13397 button.parentElement.classList.add('expansion-button-hover'); 13398 button.parentElement.classList.remove('expansion-button'); 13399 } else { 13400 button.parentElement.classList.add('expansion-button-hover'); 13401 button.parentElement.classList.remove('expansion-button'); 13402 } 13403 13404 }); 13405 13406 function handleopenCardPosition(card, expand) { 13407 const container = document.querySelector('.solutions-cards-container'); 13408 if (!card) return; 13409 console.log(card.classList) 13410 console.log(expand); 13411 13412 if (!expand) { 13413 console.log(card.classList) 13414 card.classList.remove('force-fit-content'); 13415 return; 13416 } 13417 13418 const className = 'expanded-business-card'; 13419 13420 const cardBottom = card.getBoundingClientRect().bottom + window.scrollY; 13421 const containerBottom = container.getBoundingClientRect().bottom + window.scrollY; 13422 if ((containerBottom - cardBottom) <= 500) { 13423 card.classList.add('force-fit-content'); 13424 } 13425 } 13426 13427 13428 button.addEventListener('click', (event) => { 13429 if (event.pointerType != 'mouse') { 13430 if (event.currentTarget.dataset.state == "closed") { 13431 if (openCard != undefined) { 13432 openCard.classList.remove('expanded-business-card'); 13433 const button = openCard.querySelector('button[data-state="open"]'); 13434 button.dataset.state = 'closed'; 13435 handleopenCardPosition(openCard, false); 13436 } 13437 openCard = $(event.currentTarget).closest('.card')[0]; 13438 openCard.classList.add('expanded-business-card'); 13439 handleopenCardPosition(openCard, true); 13440 event.currentTarget.dataset.state = "open"; 13441 } else { 13442 openCard.classList.remove('expanded-business-card'); 13443 event.currentTarget.dataset.state = "closed"; 13444 handleopenCardPosition(openCard, false); 13445 openCard = undefined; 13446 } 13447 if (button.parentElement.classList.contains('expansion-button-hover')) { 13448 button.parentElement.classList.remove('expansion-button-hover'); 13449 button.parentElement.classList.add('expansion-button'); 13450 } 13451 return; 13452 } 13453 if (event.currentTarget.dataset.state == "closed") { 13454 if (openCard != undefined) { 13455 openCard.classList.remove('expanded-business-card'); 13456 const button = openCard.querySelector('button[data-state="open"]'); 13457 button.dataset.state = 'closed'; 13458 handleopenCardPosition(openCard, false); 13459 } 13460 openCard = $(event.currentTarget).closest('.card')[0]; 13461 openCard.classList.add('expanded-business-card'); 13462 handleopenCardPosition(openCard, true); 13463 event.currentTarget.dataset.state = "open"; 13464 13465 } else { 13466 openCard.classList.remove('expanded-business-card'); 13467 event.currentTarget.dataset.state = "closed"; 13468 handleopenCardPosition(openCard, false); 13469 openCard = undefined; 13470 } 13471 }) 13472 }); 13473 13474 13475 13476$(document).ready(function () { 13477 const originalArray = []; 13478 var arrayLength = 0; 13479 const hiddenCards = []; 13480 var dropdownActive = false;
13481 13482 const handler = { 13483 set(target, property, value) { 13484 var array = Reflect.set(target, property, value); 13485 // Updates 13486 onArrayResize(target); 13487 // Reflect the change to the actual array 13488 return array; 13489 }, 13490 }; 13491 // Wrap the array with a Proxy 13492 const activeFiltersList = new Proxy(originalArray, handler); 13493 13494 // Custom function to be triggered on array modification 13495 function onArrayResize(arr) { 13496 if (arr.length > arrayLength) { 13497 updateFilter(); 13498 arrayLength = arr.length; 13499 } else if (arr.length < arrayLength) { 13500 updateFilter(); 13501 arrayLength = arr.length; 13502 } 13503 } 13504 13505 function updateFilter() { 13506 const allCards = document.querySelectorAll( 13507 '.business-solutions .solutions-cards-container .card' 13508 ); 13509 const activeFilters = activeFiltersList.map( 13510 (filter) => Object.keys(filter)[0] 13511 ); 13512 13513 hiddenCards.length = 0; 13514 13515 allCards.forEach((card) => { 13516 const goals = [ 13517 card.dataset.goal1, 13518 card.dataset.goal2, 13519 card.dataset.goal3, 13520 card.dataset.businesssize, 13521 ]; 13522 13523 const matchesAny = activeFilters.some((f) => goals.includes(f)); 13524 13525 if (activeFilters.length === 0 || matchesAny) { 13526 // Show card 13527 card.parentElement.classList.remove('inactive-card'); 13528 card.parentElement.style.display = 'block'; 13529 } else { 13530 // Hide card 13531 hiddenCards.push(card); 13532 card.parentElement.classList.add('inactive-card'); 13533 setTimeout(() => { 13534 card.parentElement.style.display = 'none'; 13535 }, 500); 13536 } 13537 }); 13538 13539 updateCardCount(); 13540 } 13541 13542 const allCards = document.querySelectorAll( 13543 '.business-solutions .goals-filter-container .goals-filter-tags > ul > li' 13544 ); 13545 13546 if (allCards) { 13547 allCards.forEach((buttion) => { 13548 buttion.addEventListener('click', handleButtonClick); 13549 buttion.addEventListener('touch', handleButtonClick); 13550 }); 13551 } 13552 13553 const businessSizeFilter = document.getElementById('business-size-filter'); 13554 if (businessSizeFilter) { 13555 businessSizeFilter.addEventListener('change', handleDropdownChange); 13556 } 13557 13558 function handleDropdownChange(event) { 13559 if (!dropdownActive && this.value != 'empty') { 13560 addToActiveFilters(this, event); 13561 return; 13562 } else { 13563 const filterButton = document.querySelector( 13564 '.filter-button[data-dropdown="true"]' 13565 ); 13566 handleDropdownFilterButtonClick(filterButton, event.target.selectedIndex); 13567 if (this.value != 'empty') { 13568 addToActiveFilters(this, event); 13569 } 13570 } 13571 } 13572 13573 function handleButtonClick() { 13574 const dataFilter = this.dataset.filter; 13575 const dataTitle = this.textContent.trim(); 13576 13577 if (!this.classList.contains('active-filter')) { 13578 this.classList.add('active-filter'); 13579 addToActiveFilters(this); 13580 } else { 13581 handleFilterButtonClick(this); 13582 } 13583 } 13584 function handleFilterButtonClick(originClick) { 13585 activeFiltersList.forEach((filter, index) => { 13586 if (originClick.dataset.filter in filter) { 13587 $(filter[originClick.dataset.filter].target[0]).remove(); 13588 console.log(filter[originClick.dataset.filter].origin); 13589 //if the origin is from a button 13590 filter[originClick.dataset.filter].origin.classList.remove( 13591 'active-filter' 13592 ); 13593 activeFiltersList.splice(index, 1); 13594 return; 13595 } 13596 }); 13597 } 13598 13599 function handleDropdownFilterButtonClick(originSelect, resetIndex) { 13600 activeFiltersList.forEach((filter, index) => { 13601 if (originSelect.dataset.filter in filter) { 13602 $(filter[originSelect.dataset.filter].target[0]).remove(); 13603 filter[originSelect.dataset.filter].origin.selectedIndex = resetIndex 13604 ? resetIndex 13605 : 0; 13606 dropdownActive = false; 13607 activeFiltersList.splice(index, 1); 13608 return; 13609 } 13610 }); 13611 } 13612 13613 function addToActiveFilters(activatingFilter, dropdownEvent) { 13614 var filterValue; 13615 var methodNeeded; 13616 var newHTML; 13617 if (activatingFilter.classList.contains('active-filter')) { 13618 filterValue = activatingFilter.dataset.filter; 13619 newHTML = createRemoveFilterButton( 13620 filterValue, 13621 activatingFilter.textContent.trim(), 13622 false 13623 ); 13624 methodNeeded = handleFilterButtonClick; 13625 } else { 13626 dropdownActive = true; 13627 filterValue = activatingFilter.value; 13628 newHTML = createRemoveFilterButton( 13629 filterValue, 13630 dropdownEvent.target.options[event.target.selectedIndex].text, 13631 true 13632 ); 13633 methodNeeded = handleDropdownFilterButtonClick; 13634 } 13635 activeFiltersList.push({ 13636 [filterValue]: { 13637 origin: activatingFilter, 13638 target: newHTML, 13639 }, 13640 }); 13641 const filtersList = $( 13642 '.business-solutions .active-filter-rail .filters-list' 13643 ); 13644 filtersList.append(newHTML); 13645 newHTML[0].addEventListener('click', function () { 13646 methodNeeded(this); 13647 }); 13648 newHTML[0].addEventListener('touch', function () { 13649 methodNeeded(this); 13650 }); 13651 } 13652 13653 function createRemoveFilterButton(filterValue, filterTitle, isDropdown) { 13654 const activeFilterRail = $('.business-solutions .active-filter-rail'); 13655 const iconPath = activeFilterRail.data('iconpath'); 13656 13657 return $(` 13658 <div class="filter-button" data-filter="${filterValue}" data-dropdown="${isDropdown}"> 13659 <button><span><img src="${iconPath}/filter-close-icon.svg"/></span><div>${filterTitle}</div></button> 13660 </div> 13661 `); 13662 } 13663 13664 function updateCardCount() { 13665 console.log('update card count'); 13666 const resultsCount = $( 13667 '.business-solutions .active-filter-rail .results-count' 13668 ); 13669 const resultsMessage = $('.business-solutions .no-results-container')[0]; 13670 // Select cards with data-hidden="false" 13671 var cardCount = document.querySelectorAll( 13672 '.business-solutions .solutions-cards-container .card-wrapper:not(.inactive-card)' 13673 ).length; 13674 13675 // Remove any existing HTML inside resultsCount 13676 resultsCount.empty(); 13677 13678 if (hiddenCards.length == 0 && activeFiltersList.length == 0) { 13679 if (!resultsMessage.classList.contains('hidden-container')) { 13680 resultsMessage.classList.add('hidden-container'); 13681 } 13682 return; 13683 } 13684 // If no cards are present, exit early 13685 if (cardCount === 0) { 13686 resultsCount.text('0 results'); 13687 if (resultsMessage.classList.contains('hidden-container')) { 13688 resultsMessage.classList.remove('hidden-container'); 13689 } 13690 return; 13691 } 13692 13693 // Add text displaying the card count, e.g., '3 results' 13694 if (!resultsMessage.classList.contains('hidden-container')) { 13695 resultsMessage.classList.add('hidden-container'); 13696 } 13697 13698 resultsCount.text(`${cardCount} result${cardCount > 1 ? 's' : ''}`); 13699 } 13700}); 13701 13702(function () { 13703 var offset = 500, 13704 /*duration of the top scrolling animation (in ms)*/ 13705 scroll_top_duration = 700; 13706 13707 /*Back to Top*/ 13708 $('.backtotop-button:not(.backtotop-button--fixed)').each(function () { 13709 $back_to_top = $(this); 13710 /*browser window scroll (in pixels) after which the "back to top" button is visible*/ 13711 13712 13713 /*hide or show the "back to top" link*/ 13714 $(window).scroll(function () { 13715 ($(this).scrollTop() > offset) ? $back_to_top.addClass('backtotop-button--visible'): $back_to_top.removeClass('backtotop-button--visible'); 13716 }); 13717 /*smooth scroll to top*/ 13718 13719 }); 13720 13721 $('.backtotop-button').on('click', function (e) { 13722 e.preventDefault(); 13723 $('body,html').animate({ 13724 scrollTop: 0 13725 }, scroll_top_duration); 13726 }); 13727 13728}()); 13729$(function () { 13730 $('.search-icon--js').click(function () { 13731 var url = $(this).closest('form').submit(); 13732 }); 13733 $('.categories-dropdown--button').click(function(){ 13734 $('.categories-dropdown').toggle(); 13735 $(this).toggleClass('categories-dropdown--button-on') 13736 }) 13737 13738 13739 $('document').ready(function(){ 13740 var list = $('.article-category-quick-links-container ul'); 13741 if(list.length > 0) 13742 { 13743 $('.article-category-quick-links-container ul').clone().appendTo('.categories-dropdown') 13744 } 13745 }); 13746 13747 $('document').on('resize', function(){ 13748 $('.categories-dropdown').hide(); 13749 $('.categories-dropdown--button').removeClass('categories-dropdown--button-on'); 13750 }); 13751}); 13752 13753(function() { 13754 var SCREEN_MD = 992, 13755 prevMd, 13756 articleCarouselSwiper = undefined; 13757 13758 //custom function 13759 function initArticleCarouselSwiper() { 13760 var md = window.matchMedia('(min-width: ' + SCREEN_MD + 'px)').matches; 13761 if (md != prevMd || !prevMd) { 13762 if (md && articleCarouselSwiper != undefined) { 13763 // destroy the swiper on desktop (992px and up)
13764 $('.article-carousel').each(function() { 13765 articleCarouselSwiper.destroy(true, true); 13766 articleCarouselSwiper = undefined; 13767 }); 13768 } else if (!md && articleCarouselSwiper == undefined) { 13769 // initialize the swiper on mobile 13770 $('.article-carousel').each(function() { 13771 var $this = $(this); 13772 articleCarouselSwiper = new Swiper($this, { 13773 speed: 400, 13774 spaceBetween: 20, 13775 slidesPerView: 3, 13776 pagination: { 13777 el: $this.children('.swiper-pagination'), 13778 type: 'bullets', 13779 clickable: true 13780 }, 13781 breakpoints: { 13782 // when window width is <= 991px 13783 991: { 13784 slidesPerView: 1 13785 } 13786 } 13787 }); 13788 }); 13789 } 13790 prevMd = md; 13791 } 13792 } 13793 13794 // initiate slider 13795 $(window).on('load', function() { 13796 initArticleCarouselSwiper(); 13797 }); 13798 13799 $(window).on('resize', function() { 13800 initArticleCarouselSwiper(); 13801 }); 13802})(); 13803 13804 13805$(function () { 13806 var components = $('.articleGrid'); 13807 $('.article--grid-filters select') 13808 .selectBoxIt() 13809 .change(function () { 13810 var currentCount = $('#article-grid-data .articleblock').length; 13811 var sortLimit = Math.max(currentCount, limit); 13812 articleStart = 0; 13813 $('#article-grid-data').empty(); 13814 loadMore(sortLimit); 13815 }); 13816 13817 var articleStart = 0; 13818 var articleMax = 0; 13819 var limit = $('.article--grid-container').data('maxarticles'); 13820 var lazyload = $('.article--grid-container').data('lazyload'); 13821 var alt = $('.article--grid-data').hasClass('alternate'); 13822 var gridPath; 13823 var gridTags; 13824 var enableDescription = $('.articlegrid-short-description'); 13825 var showAuthor = $('.article--grid-container').attr('data-show-author') === 'true'; 13826 13827 var SCREEN_MD = 992, 13828 prevMd, 13829 imgSelector = '.articleblock--image-container img'; 13830 13831 if (components.length) { 13832 gridPath = encodeURIComponent( 13833 $(this).find('#article-grid-data').data('path') 13834 ); 13835 gridTags = encodeURIComponent( 13836 $(this).find('#article-grid-data').data('tags') 13837 ); 13838 13839 $('#loadMore').show(); 13840 //Get all the articles 13841 loadMore(); 13842 } 13843 13844 function loadMore(overrideLimit) { 13845 var currentLimit = overrideLimit || limit; 13846 var xhttp = new XMLHttpRequest(); 13847 xhttp.onreadystatechange = function () { 13848 if (this.readyState == 4 && this.status == 200) { 13849 renderHTML(this.responseText); 13850 } 13851 }; 13852 13853 var filter = encodeURIComponent($('.article--grid-filters select').val()); 13854 13855 xhttp.open( 13856 'GET', 13857 '/bin/southern/global/latestarticles?tags=' + 13858 gridTags + 13859 '&path=' + 13860 gridPath + 13861 '&start=' + 13862 articleStart + 13863 '&limit=' + 13864 currentLimit + 13865 '&filter=' + 13866 filter, 13867 true 13868 ); 13869 xhttp.send(); 13870 } 13871 13872 function renderHTML(articles) { 13873 var articlesArr = JSON.parse(articles); 13874 articleMax = articlesArr.size; 13875 articleStart += articlesArr.pages.length; 13876 if (articleStart >= articleMax) { 13877 $('#loadMore').hide(); 13878 } else { 13879 $('#loadMore').show(); 13880 } 13881 jQuery.each(articlesArr.pages, function (index, obj) { 13882 var redirect = obj.customRedirect ? obj.customRedirect : obj.path; 13883 console.log(lazyload); 13884 var lazystring = lazyload == 'disable' ? '' : ' loading="lazy" ' ; 13885 var shortDescription = enableDescription.length == 1 ? '<a class="articleblock--content--description" href="' + redirect + '"><p>' + obj.description + '</p></a>' : ''; 13886 var authorHTML = ''; 13887 if (showAuthor && obj.author) { 13888 authorHTML = obj.authorLink 13889 ? '<div class="articleblock--content--author"><a class="articleblock--content--author-link" href="' + obj.authorLink + '" aria-label="' + obj.author + '">' + obj.author + '</a></div>' 13890 : '<div class="articleblock--content--author">' + obj.author + '</div>'; 13891 } 13892 var tagClasses = obj.tagClasses ? obj.tagClasses : ''; 13893 var imageAlt = escapeHtmlAttr(obj.articlePromoImageAlt); 13894 var articlesHTML = 13895 '<div class="articleblock ' + tagClasses + '"><div class="articleblock--image-container"> <a href="' + 13896 redirect + 13897 '"> <img class="articleblock--image"' + lazystring + 'src="' + 13898 obj.articlePromoImage + 13899 '" alt="' + 13900 imageAlt + 13901 '" /></div></a><div class="articleblock--content"><p class="articleblock--content--tag">' + 13902 obj.primaryTag + 13903 '</p><a class="articleblock--content--title" href="' + 13904 redirect + 13905 '">' + 13906 obj.title + 13907 '</a>' + 13908 shortDescription + 13909 authorHTML + 13910 '</div></div>'; 13911 $('#article-grid-data').append(articlesHTML); 13912 }); 13913 setTitleEllipsis(); 13914 } 13915 13916 function escapeHtmlAttr(value) { 13917 if (value == null || value === '') { 13918 return ''; 13919 } 13920 return String(value) 13921 .replace(/&/g, '&') 13922 .replace(/"/g, '"') 13923 .replace(/'/g, ''') 13924 .replace(/</g, '<') 13925 .replace(/>/g, '>'); 13926 } 13927 13928 function setTitleEllipsis() { 13929 $('.ellip').contents().unwrap(); 13930 $('.ellip-line').contents().unwrap(); 13931 if (window.innerWidth > 992) { 13932 if (alt) { 13933 $('.articleblock--content--title').ellipsis({ lines: 2 }); 13934 } else { 13935 $('.articleblock--content--title').ellipsis({ lines: 4 }); 13936 } 13937 } 13938 } 13939 13940 $(window).on('resize', function () { 13941 setTitleEllipsis(); 13942 }); 13943 13944 $('#loadMore').click(function () {
13945 loadMore(); 13946 }); 13947}); 13948 13949 13950 13951document.addEventListener('DOMContentLoaded', function() { 13952 // Collect all image series containers 13953 const containers = document.querySelectorAll('.animation-container.image-series'); 13954 13955 // Store all instances for coordinated refresh 13956 const instances = []; 13957 13958 // Initialize all instances first, then do a single global refresh 13959 containers.forEach(function(container, index) { 13960 const instanceId = container.id; // Uses model.instanceId from HTML template 13961 13962 let imagePathsJson = container.getAttribute('data-image-series-paths'); 13963 13964 if (!imagePathsJson) { 13965 console.warn('No image series paths found for container:', instanceId); 13966 return; 13967 } 13968 13969 let imagePaths = []; 13970 try { 13971 imagePaths = JSON.parse(imagePathsJson); 13972 } catch (e) { 13973 console.error('Failed to parse image series paths for container:', instanceId, e); 13974 return; 13975 } 13976 13977 if (imagePaths.length === 0) { 13978 console.warn('No images found in image series folder for container:', instanceId); 13979 return; 13980 } 13981 13982 // Find canvas element 13983 const canvas = container.querySelector('canvas'); 13984 if (!canvas) { 13985 console.warn('Canvas element not found in container:', instanceId); 13986 return; 13987 } 13988 13989 // Check if content container scroll is enabled 13990 const useContentContainerScroll = container.getAttribute('data-use-content-container-scroll') === 'true'; 13991 13992 // Find content container 13993 const contentContainer = container.querySelector('.content-container'); 13994 13995 // For content-container scroll mode, we need the content container and its scrollable content 13996 let scrollableContent = null; 13997 if (useContentContainerScroll) { 13998 if (!contentContainer) { 13999 console.warn('Content container not found in:', instanceId); 14000 return; 14001 } 14002 scrollableContent = contentContainer.firstElementChild; 14003 if (!scrollableContent) { 14004 console.warn('No scrollable content found in content-container:', instanceId); 14005 return; 14006 } 14007 } 14008 14009 // Parse overlay items data (will apply to content-container background) 14010 const overlayData = parseOverlayData(container, instanceId); 14011 14012 // Set canvas to match container's actual rendered size 14013 const setCanvasSize = function() { 14014 const containerStyle = window.getComputedStyle(container); 14015 const containerRect = container.getBoundingClientRect(); 14016 14017 let targetWidth = containerRect.width; 14018 let targetHeight = containerRect.height; 14019 14020 if (contentContainer) { 14021 const contentRect = contentContainer.getBoundingClientRect(); 14022 14023 const widthIsFitContent = containerStyle.width === 'fit-content' || 14024 containerStyle.width === 'max-content' || 14025 containerStyle.width === 'min-content'; 14026 if (widthIsFitContent && contentRect.width > 0) { 14027 targetWidth = contentRect.width; 14028 } 14029 14030 const heightIsFitContent = containerStyle.height === 'fit-content' || 14031 containerStyle.height === 'max-content' || 14032 containerStyle.height === 'min-content'; 14033 if (heightIsFitContent && contentRect.height > 0) { 14034 targetHeight = contentRect.height; 14035 } 14036 } 14037 14038 canvas.width = targetWidth; 14039 canvas.height = targetHeight; 14040 }; 14041 14042 setCanvasSize(); 14043 14044 // Build ScrollTrigger config based on scroll mode 14045 const scrollTriggerConfig = { 14046 id: 'imageSeq-' + instanceId, 14047 scrub: true, 14048 refreshPriority: -index 14049 }; 14050 14051 if (useContentContainerScroll) { 14052 // Content container scroll mode: track scrolling within the content-container 14053 scrollTriggerConfig.scroller = contentContainer; 14054 scrollTriggerConfig.trigger = scrollableContent; 14055 scrollTriggerConfig.start = "top center"; // Frame 0 when content top reaches container center 14056 scrollTriggerConfig.end = "bottom center"; // Last frame when content bottom reaches container center 14057 } else { 14058 // Page scroll mode: track page scrolling relative to the animation container 14059 scrollTriggerConfig.trigger = container; 14060 scrollTriggerConfig.start = "top center"; // Frame 0 when container top reaches viewport center 14061 scrollTriggerConfig.end = "bottom center"; // Last frame when container bottom reaches viewport center 14062 } 14063 14064 // Initialize image sequence animation with unique ScrollTrigger ID 14065 const animationData = imageSequence({ 14066 urls: imagePaths, 14067 canvas: canvas, 14068 overlayData: overlayData, 14069 contentContainer: contentContainer, 14070 scrollTrigger: scrollTriggerConfig 14071 }); 14072 14073 const animation = animationData.animation;
14074 const forceUpdate = animationData.update; 14075 14076 // Check if pin animation is enabled via data attribute 14077 const pinAnimationEnabled = container.getAttribute('data-pin-animation-enabled') === 'true'; 14078 let pinAnimation = null; 14079 14080 // Add parallax pin effect with unique ID and anticipatePin 14081 if (pinAnimationEnabled) { 14082 pinAnimation = gsap.to(canvas, { 14083 scrollTrigger: { 14084 id: 'pin-' + instanceId, 14085 trigger: container, 14086 start: "top top", 14087 end: "bottom top", 14088 pin: canvas, 14089 pinSpacing: false, 14090 anticipatePin: 1, 14091 refreshPriority: -index // Higher on page = higher priority 14092 } 14093 }); 14094 } 14095 14096 // Store instance data for coordinated refresh 14097 instances.push({ 14098 instanceId: instanceId, 14099 container: container, 14100 canvas: canvas, 14101 contentContainer: contentContainer, 14102 animation: animation, 14103 pinAnimation: pinAnimation, 14104 forceUpdate: forceUpdate, 14105 forceRedraw: animationData.forceRedraw, 14106 setCanvasSize: setCanvasSize, 14107 overlayData: overlayData 14108 }); 14109 }); 14110 14111 // Single global refresh after ALL instances are initialized 14112 if (instances.length > 0) { 14113 setTimeout(function() { 14114 ScrollTrigger.refresh(); 14115 instances.forEach(function(inst) { 14116 if (inst.forceRedraw) { 14117 inst.forceRedraw(); 14118 } 14119 }); 14120 }, 100); 14121 } 14122 14123 // Single global resize handler for all instances 14124 let resizeTimeout; 14125 window.addEventListener('resize', function() { 14126 clearTimeout(resizeTimeout); 14127 resizeTimeout = setTimeout(function() { 14128 // Update all canvas sizes 14129 instances.forEach(function(inst) { 14130 inst.setCanvasSize(); 14131 }); 14132 14133 // Single coordinated refresh for all ScrollTriggers 14134 ScrollTrigger.refresh(); 14135 14136 // Force redraw at current position for all instances 14137 instances.forEach(function(inst) { 14138 if (inst.forceRedraw) { 14139 inst.forceRedraw(); 14140 } 14141 }); 14142 }, 250); 14143 }); 14144 14145 /** 14146 * Parse overlay items from data attribute 14147 * @param {HTMLElement} container - The animation container element 14148 * @param {string} instanceId - Unique instance ID for this container 14149 * @returns {Array} Array of overlay data objects with frame ranges and colors 14150 */ 14151 function parseOverlayData(container, instanceId) { 14152 const overlayItemsJson = container.getAttribute('data-overlay-items'); 14153 const overlayData = []; 14154 14155 if (!overlayItemsJson) { 14156 return overlayData; 14157 } 14158 14159 let overlayItems = []; 14160 try { 14161 overlayItems = JSON.parse(overlayItemsJson); 14162 } catch (e) { 14163 console.error('Failed to parse overlay items for container:', instanceId, e); 14164 return overlayData; 14165 } 14166 14167 if (overlayItems.length === 0) { 14168 return overlayData; 14169 } 14170 14171 overlayItems.forEach(function(item, i) { 14172 overlayData.push({ 14173 beginImageNumber: item.beginImageNumber !== null ? parseInt(item.beginImageNumber, 10) : null, 14174 endImageNumber: item.endImageNumber !== null ? parseInt(item.endImageNumber, 10) : null, 14175 backgroundColor: item.backgroundColor || 'transparent' 14176 }); 14177 }); 14178 14179 return overlayData; 14180 } 14181 14182 /** 14183 * Update content-container background based on current frame 14184 * @param {number} currentFrame - The current frame number 14185 * @param {Array} overlayData - Array of overlay data objects with frame ranges and colors 14186 * @param {HTMLElement} contentContainer - The content-container element to apply background to 14187 */ 14188 function updateOverlays(currentFrame, overlayData, contentContainer) { 14189 if (!contentContainer || !overlayData || overlayData.length === 0) { 14190 return; 14191 } 14192 14193 // Find the active overlay for the current frame
14194 let activeBackground = ''; 14195 14196 for (let i = 0; i < overlayData.length; i++) { 14197 const overlay = overlayData[i]; 14198 const begin = overlay.beginImageNumber; 14199 const end = overlay.endImageNumber; 14200 14201 // Skip if no begin number defined 14202 if (begin === null || begin === undefined) { 14203 continue; 14204 } 14205 14206 let isActive = false; 14207 14208 if (end !== null && end !== undefined) { 14209 // Range mode: active when frame is between begin and end (inclusive) 14210 isActive = currentFrame >= begin && currentFrame <= end; 14211 } else { 14212 // Single image mode: active only on exact frame 14213 isActive = currentFrame === begin; 14214 } 14215 14216 if (isActive) { 14217 activeBackground = overlay.backgroundColor; 14218 break; // Use first matching overlay 14219 } 14220 } 14221 14222 // Apply background to content-container (CSS transition handles fading) 14223 contentContainer.style.background = activeBackground; 14224 } 14225 14226 /** 14227 * Helper function that handles scrubbing through a sequence of images, 14228 * drawing the appropriate one to the provided canvas. 14229 * 14230 * Config object properties: 14231 * - urls [Array]: an Array of image URLs 14232 * - canvas [Canvas]: the <canvas> object to draw to 14233 * - scrollTrigger [Object]: an optional ScrollTrigger configuration object 14234 * - clear [Boolean]: if true, it'll clear out the canvas before drawing each frame 14235 * - paused [Boolean]: true if you'd like the returned animation to be paused initially 14236 * - fps [Number]: optional frames per second. Defaults to 30fps. 14237 * - onUpdate [Function]: optional callback for when the Tween updates 14238 * - overlayData [Array]: optional array of overlay data for content-container background 14239 * - contentContainer [HTMLElement]: the content-container element for background overlays 14240 * 14241 * Returns an object with animation and update function 14242 */ 14243 function imageSequence(config) { 14244 const playhead = {frame: 0}; 14245 const canvas = config.canvas; 14246 const overlayData = config.overlayData || []; 14247 const contentContainer = config.contentContainer; 14248 14249 if (!canvas) { 14250 console.warn("canvas not defined"); 14251 return { animation: null, update: null, forceRedraw: null }; 14252 } 14253 14254 const ctx = canvas.getContext("2d"); 14255 let curFrame = -1; 14256 const onUpdate = config.onUpdate; 14257 const totalFrames = config.urls.length; 14258 14259 const images = new Array(totalFrames); 14260 for (let i = 0; i < totalFrames; i++) { 14261 images[i] = new Image(); 14262 } 14263 14264 const updateImage = function() { 14265 const frame = Math.round(playhead.frame); 14266 if (frame !== curFrame) { 14267 if (config.clear) { 14268 ctx.clearRect(0, 0, canvas.width, canvas.height); 14269 } 14270 14271 const img = images[frame]; 14272 if (img && img.complete && img.naturalWidth > 0) { 14273 const scale = Math.max( 14274 canvas.width / img.width, 14275 canvas.height / img.height 14276 ); 14277 const x = (canvas.width / 2) - (img.width / 2) * scale; 14278 const y = (canvas.height / 2) - (img.height / 2) * scale; 14279 ctx.drawImage(img, x, y, img.width * scale, img.height * scale); 14280 } 14281 14282 curFrame = frame; 14283 14284 updateOverlays(frame, overlayData, contentContainer); 14285 14286 if (onUpdate) { 14287 onUpdate.call(this, frame, images[frame]); 14288 } 14289 } 14290 }; 14291 14292 const forceRedraw = function() { 14293 curFrame = -1; 14294 updateImage(); 14295 }; 14296 14297 // --- Viewport-aware priority image loader --- 14298 var loadGeneration = 0; 14299 var loadedCount = 0; 14300 var BATCH_SIZE = 4; 14301 14302 function buildSpiralOrder(center, total) { 14303 const order = [center]; 14304 var offset = 1; 14305 while (order.length < total) { 14306 if (center + offset < total) order.push(center + offset); 14307 if (center - offset >= 0) order.push(center - offset); 14308 offset++; 14309 } 14310 return order; 14311 } 14312 14313 function loadImagesFromScrollPosition(startFrame) { 14314 var generation = ++loadGeneration; 14315 var order = buildSpiralOrder(startFrame, totalFrames); 14316 var queueIndex = 0; 14317 14318 function loadNextBatch() { 14319 if (generation !== loadGeneration) return; 14320 14321 var loading = 0; 14322 while (queueIndex < order.length && loading < BATCH_SIZE) { 14323 var frameIdx = order[queueIndex]; 14324 queueIndex++; 14325 14326 if (images[frameIdx].src) continue; 14327 14328 (function(idx) { 14329 images[idx].onload = function() { 14330 loadedCount++; 14331 if (idx === Math.round(playhead.frame)) { 14332 forceRedraw(); 14333 } 14334 if (generation === loadGeneration) { 14335 loadNextBatch(); 14336 } 14337 }; 14338 images[idx].onerror = function() { 14339 console.warn('Failed to load image at index', idx, config.urls[idx]); 14340 if (generation === loadGeneration) { 14341 loadNextBatch(); 14342 } 14343 }; 14344 images[idx].src = config.urls[idx]; 14345 })(frameIdx); 14346 14347 loading++; 14348 } 14349 } 14350 14351 loadNextBatch(); 14352 } 14353 14354 // Create GSAP animation with ScrollTrigger 14355 const animation = gsap.to(playhead, { 14356 frame: totalFrames - 1, 14357 ease: "none", 14358 onUpdate: updateImage, 14359 duration: totalFrames / (config.fps || 30), 14360 paused: !!config.paused, 14361 scrollTrigger: config.scrollTrigger 14362 }); 14363 14364 // Determine initial frame from current scroll position, then start loading 14365 var scrollTriggerInstance = animation.scrollTrigger; 14366 var initialProgress = scrollTriggerInstance ? scrollTriggerInstance.progress : 0; 14367 var initialFrame = Math.round(initialProgress * (totalFrames - 1)); 14368 loadImagesFromScrollPosition(initialFrame); 14369 14370 // Re-prioritize loading when user scrolls to unloaded frames (throttled) 14371 var reprioritizeTimeout; 14372 if (scrollTriggerInstance) {
14373 scrollTriggerInstance.vars.onUpdate = function() { 14374 updateImage(); 14375 14376 if (loadedCount >= totalFrames) return; 14377 14378 clearTimeout(reprioritizeTimeout); 14379 reprioritizeTimeout = setTimeout(function() { 14380 var currentFrame = Math.round(playhead.frame); 14381 if (!images[currentFrame].src || !images[currentFrame].complete) { 14382 loadImagesFromScrollPosition(currentFrame); 14383 } 14384 }, 150); 14385 }; 14386 } 14387 14388 return { 14389 animation: animation, 14390 update: updateImage, 14391 forceRedraw: forceRedraw 14392 }; 14393 } 14394 14395}); 14396 14397(function () { 14398 "use strict"; 14399 14400 var SELECTOR = '[data-cmp-is="animatedcounter"]'; 14401 var NUMBER_SELECTOR = ".cmp-animated-counter__number"; 14402 var counters = []; 14403 14404 var initCounter = function (el) { 14405 var numberEl = el.querySelector(NUMBER_SELECTOR); 14406 if (!numberEl) return; 14407 14408 var rawValue = el.dataset.animatedValue || "0"; 14409 var endValue = parseFloat(rawValue) || 0; 14410 if (endValue === 0) return; 14411 14412 var enableCommas = el.getAttribute("data-enable-commas") === "true"; 14413 var dotIndex = rawValue.indexOf("."); 14414 var decimalPlaces = dotIndex >= 0 ? rawValue.length - dotIndex - 1 : 0; 14415 14416 var format = enableCommas ? "(,ddd)" : "(ddd)"; 14417 if (decimalPlaces > 0) { 14418 var decimals = ""; 14419 for (var d = 0; d < decimalPlaces; d++) { decimals += "d"; } 14420 format += "." + decimals; 14421 } 14422 14423 var od = new Odometer({ 14424 el: numberEl, 14425 value: 0, 14426 format: format 14427 }); 14428 14429 counters.push({ 14430 container: el, 14431 odometer: od, 14432 target: endValue, 14433 animated: false 14434 }); 14435 }; 14436 14437 var isInViewport = function (el) { 14438 var rect = el.getBoundingClientRect(); 14439 var threshold = window.innerWidth < 768 ? 1150 : 900; 14440 return rect.top <= threshold && rect.top >= -700; 14441 }; 14442 14443 var handleScroll = function () { 14444 for (var i = 0; i < counters.length; i++) { 14445 if (counters[i].animated) continue; 14446 14447 if (isInViewport(counters[i].container)) { 14448 counters[i].odometer.update(counters[i].target); 14449 counters[i].animated = true; 14450 } 14451 } 14452 }; 14453 14454 var handleDOMReady = function () { 14455 var elements = document.querySelectorAll(SELECTOR); 14456 elements.forEach(initCounter); 14457 14458 if (counters.length > 0) { 14459 window.addEventListener("scroll", handleScroll); 14460 handleScroll(); 14461 } 14462 }; 14463 14464 if (document.readyState === "loading") { 14465 document.addEventListener("DOMContentLoaded", handleDOMReady); 14466 } else { 14467 handleDOMReady(); 14468 } 14469})(); 14470 14471(function() { 14472 "use strict"; 14473 14474 // Capture and temporarily strip the URL hash to prevent the browser's native 14475 // scroll-to-hash behavior, which causes erratic scrolling in Firefox/Safari 14476 // as layout shifts during page load. The hash is restored after the custom 14477 // GSAP scroll completes, or immediately if it doesn't match an anchor-component. 14478 var initialHash = window.location.hash; 14479 if (initialHash) { 14480 try { 14481 history.replaceState(null, null, 14482 window.location.pathname + window.location.search); 14483 } catch(e) { 14484 initialHash = null; 14485 } 14486 } 14487 14488 // Tracks whether the current scroll was triggered by the initial page-load hash, 14489 // so performScroll can use replaceState (no extra history entry) instead of pushState. 14490 var isInitialScrollInProgress = false; 14491 14492 /** 14493 * Performs the actual scroll animation to an element 14494 * @param {Element} anchorElement - The anchor element to scroll to 14495 * @param {string} hash - The hash to push to URL after scrolling 14496 * @param {number} scrollAdjustment - Pixel adjustment for scroll position 14497 */ 14498 var performScroll = function(anchorElement, hash, scrollAdjustment) { 14499 var scrollPosition = anchorElement.getBoundingClientRect().top + window.pageYOffset + scrollAdjustment - 150; 14500 14501 var currentPosition = window.pageYOffset; 14502 var distance = Math.abs(scrollPosition - currentPosition); 14503 var pixelsPerSecond = 1200; 14504 var duration = distance / pixelsPerSecond; 14505 duration = Math.max(0.3, Math.min(1.5, duration)); 14506 14507 var useReplaceState = isInitialScrollInProgress; 14508 14509 gsap.to(window, { 14510 duration: duration, 14511 scrollTo: { y: scrollPosition }, 14512 ease: "power2.out", 14513 onComplete: function() { 14514 if (hash && window.location.hash !== hash) { 14515 if (useReplaceState) { 14516 window.history.replaceState(null, null, hash); 14517 } else { 14518 window.history.pushState(null, null, hash); 14519 } 14520 } 14521 isInitialScrollInProgress = false;
14522 } 14523 }); 14524 }; 14525 14526 /** 14527 * Handle accordion anchor type - open accordion if needed, then scroll 14528 * @param {Element} anchorElement - The anchor element inside an accordion 14529 * @param {string} hash - The hash to push to URL after scrolling 14530 * @param {number} scrollAdjustment - Pixel adjustment for scroll position 14531 */ 14532 var handleAccordionAnchor = function(anchorElement, hash, scrollAdjustment) { 14533 var accordionContent = anchorElement.closest(".accordion__content"); 14534 14535 if (!accordionContent) { 14536 // Not inside an accordion, fall back to default scroll 14537 performScroll(anchorElement, hash, scrollAdjustment); 14538 return; 14539 } 14540 14541 var accordionItem = accordionContent.closest(".accordion__item"); 14542 var accordionTitle = accordionContent.previousElementSibling; 14543 14544 // Check if accordion is already open 14545 if (accordionItem && accordionItem.classList.contains("is-active")) { 14546 // Already open, just scroll 14547 performScroll(anchorElement, hash, scrollAdjustment); 14548 } else if (accordionTitle) { 14549 // Trigger click to open accordion 14550 accordionTitle.click(); 14551 14552 // Wait for slideDown animation (400ms) + buffer, then scroll 14553 setTimeout(function() { 14554 performScroll(anchorElement, hash, scrollAdjustment); 14555 }, 450); 14556 } 14557 }; 14558 14559 /** 14560 * Handle tab anchor type - activate tab if needed, then scroll 14561 * Follows AEM Core Components tabs v1 pattern 14562 * @param {Element} anchorElement - The anchor element inside a tab panel 14563 * @param {string} hash - The hash to push to URL after scrolling 14564 * @param {number} scrollAdjustment - Pixel adjustment for scroll position 14565 */ 14566 var handleTabAnchor = function(anchorElement, hash, scrollAdjustment) { 14567 var tabPanel = anchorElement.closest("[data-cmp-hook-tabs='tabpanel']"); 14568 14569 if (!tabPanel) { 14570 // Not inside a tab panel, fall back to default scroll 14571 performScroll(anchorElement, hash, scrollAdjustment); 14572 return; 14573 } 14574 14575 // Check if tab panel is already active 14576 if (tabPanel.classList.contains("cmp-tabs__tabpanel--active")) { 14577 // Already active, just scroll 14578 performScroll(anchorElement, hash, scrollAdjustment); 14579 return; 14580 } 14581 14582 // Find the tabs container and determine the index of this panel 14583 // Note: Using .cmp-tabs class instead of [data-cmp-is='tabs'] because 14584 // the data attribute is removed after tabs component initialization 14585 var tabsContainer = tabPanel.closest(".cmp-tabs"); 14586 if (!tabsContainer) { 14587 performScroll(anchorElement, hash, scrollAdjustment); 14588 return; 14589 } 14590 14591 var tabPanels = tabsContainer.querySelectorAll("[data-cmp-hook-tabs='tabpanel']"); 14592 var tabs = tabsContainer.querySelectorAll("[data-cmp-hook-tabs='tab']"); 14593 var panelIndex = Array.prototype.indexOf.call(tabPanels, tabPanel); 14594 14595 if (panelIndex > -1 && tabs[panelIndex]) { 14596 // Click the corresponding tab to activate it 14597 tabs[panelIndex].click(); 14598 14599 // Wait for tab switch and any native scroll to complete 14600 // Use longer delay + requestAnimationFrame to ensure layout is settled 14601 setTimeout(function() { 14602 requestAnimationFrame(function() { 14603 performScroll(anchorElement, hash, scrollAdjustment); 14604 }); 14605 }, 800); 14606 } else { 14607 performScroll(anchorElement, hash, scrollAdjustment); 14608 } 14609 }; 14610 14611 /** 14612 * Handle carousel anchor type - navigate to carousel slide if needed, then scroll 14613 * Follows AEM Core Components carousel v1 pattern 14614 * @param {Element} anchorElement - The anchor element inside a carousel item 14615 * @param {string} hash - The hash to push to URL after scrolling 14616 * @param {number} scrollAdjustment - Pixel adjustment for scroll position 14617 */ 14618 var handleCarouselAnchor = function(anchorElement, hash, scrollAdjustment) { 14619 // Try data attribute selector first, then fallback to class selector 14620 var carouselItem = anchorElement.closest("[data-cmp-hook-carousel='item']"); 14621 14622 if (!carouselItem) { 14623 // Fallback: try finding by class (some implementations may not have data attributes) 14624 carouselItem = anchorElement.closest(".cmp-carousel__item"); 14625 } 14626 14627 if (!carouselItem) { 14628 // Not inside a carousel item, fall back to default scroll 14629 performScroll(anchorElement, hash, scrollAdjustment); 14630 return; 14631 } 14632 14633 // Check if carousel item is already active 14634 if (carouselItem.classList.contains("cmp-carousel__item--active")) { 14635 // Already active, just scroll 14636 performScroll(anchorElement, hash, scrollAdjustment); 14637 return; 14638 } 14639 14640 // Find the carousel container 14641 var carouselContainer = carouselItem.closest(".cmp-carousel"); 14642 14643 if (!carouselContainer) { 14644 // Fallback: try data attribute selector 14645 carouselContainer = carouselItem.closest("[data-cmp-is='carousel']"); 14646 } 14647 14648 if (!carouselContainer) { 14649 performScroll(anchorElement, hash, scrollAdjustment); 14650 return; 14651 } 14652 14653 // Get all carousel items - try data attribute first, then class fallback 14654 var carouselItems = carouselContainer.querySelectorAll("[data-cmp-hook-carousel='item']"); 14655 if (!carouselItems.length) { 14656 carouselItems = carouselContainer.querySelectorAll(".cmp-carousel__item"); 14657 } 14658 14659 var itemIndex = Array.prototype.indexOf.call(carouselItems, carouselItem); 14660 14661 // Get indicators - try data attribute first, then class fallback 14662 var indicators = carouselContainer.querySelectorAll("[data-cmp-hook-carousel='indicator']"); 14663 if (!indicators.length) { 14664 indicators = carouselContainer.querySelectorAll(".cmp-carousel__indicator"); 14665 } 14666 14667 if (itemIndex > -1 && indicators[itemIndex]) { 14668 // Click the corresponding indicator to navigate to the slide 14669 indicators[itemIndex].click(); 14670 14671 // Wait for carousel transition and any native scroll to complete 14672 // Use longer delay + requestAnimationFrame to ensure layout is settled 14673 setTimeout(function() { 14674 requestAnimationFrame(function() { 14675 performScroll(anchorElement, hash, scrollAdjustment); 14676 }); 14677 }, 800); 14678 } else { 14679 // No indicators found or invalid index - try direct scroll anyway 14680 performScroll(anchorElement, hash, scrollAdjustment); 14681 } 14682 }; 14683 14684 /** 14685 * Check if generalModalAPI is available 14686 * @returns {boolean} 14687 */ 14688 var hasModalAPI = function() { 14689 return typeof window.generalModalAPI !== 'undefined'; 14690 }; 14691 14692 /** 14693 * Performs scroll animation within a modal's .background element 14694 * @param {Element} anchorElement - The anchor element to scroll to 14695 * @param {string} hash - The hash to push to URL after scrolling 14696 * @param {number} scrollAdjustment - Pixel adjustment for scroll position 14697 * @param {Element}
14697 modalBackground - The modal's .background element 14698 */ 14699 var performModalScroll = function(anchorElement, hash, scrollAdjustment, modalBackground) { 14700 // Scroll the .background element directly 14701 var scrollableContent = modalBackground; 14702 14703 var anchorRect = anchorElement.getBoundingClientRect(); 14704 var containerRect = scrollableContent.getBoundingClientRect(); 14705 var scrollPosition = anchorRect.top - containerRect.top + scrollableContent.scrollTop + scrollAdjustment; 14706 14707 // Calculate duration based on scroll distance 14708 var currentPosition = scrollableContent.scrollTop; 14709 var distance = Math.abs(scrollPosition - currentPosition); 14710 var pixelsPerSecond = 1200; 14711 var duration = distance / pixelsPerSecond; 14712 duration = Math.max(0.3, Math.min(1.0, duration)); 14713 14714 var useReplaceState = isInitialScrollInProgress; 14715 14716 gsap.to(scrollableContent, { 14717 duration: duration, 14718 scrollTo: { y: scrollPosition }, 14719 ease: "power2.out", 14720 onComplete: function() { 14721 if (hash && window.location.hash !== hash) { 14722 if (useReplaceState) { 14723 window.history.replaceState(null, null, hash); 14724 } else { 14725 window.history.pushState(null, null, hash); 14726 } 14727 } 14728 isInitialScrollInProgress = false; 14729 } 14730 }); 14731 }; 14732 14733 /** 14734 * Handle modal anchor type - open modal if needed, then scroll within it 14735 * Uses window.generalModalAPI for coordination with generalmodal.js 14736 * @param {Element} anchorElement - The anchor element inside a modal 14737 * @param {string} hash - The hash to push to URL after scrolling 14738 * @param {number} scrollAdjustment - Pixel adjustment for scroll position 14739 */ 14740 var handleModalAnchor = function(anchorElement, hash, scrollAdjustment) { 14741 var modalId = anchorElement.dataset.modalId; 14742 14743 if (!modalId) { 14744 // No modal ID, fall back to default scroll 14745 performScroll(anchorElement, hash, scrollAdjustment); 14746 return; 14747 } 14748 14749 // Check if API is available 14750 if (!hasModalAPI()) { 14751 console.warn("generalModalAPI not available for modal anchor"); 14752 return; 14753 } 14754 14755 // Check if anchor is already in the currently open modal (just scroll, don't close/reopen) 14756 var currentModal = window.generalModalAPI.getCurrentModal(); 14757 if (currentModal) { 14758 var currentModalId = currentModal.getAttribute('anchorId'); 14759 if (currentModalId === modalId) { 14760 // Same modal - just scroll within it 14761 performModalScroll(anchorElement, hash, scrollAdjustment, currentModal); 14762 return; 14763 } 14764 } 14765 14766 // Close any open modals first (preserve scroll lock since we're opening another) 14767 if (window.generalModalAPI.isOpen()) { 14768 window.generalModalAPI.closeModal({ preserveScrollLock: true }); 14769 } 14770 14771 // Open the target modal via API 14772 var modalBackground = window.generalModalAPI.openModal(modalId); 14773 if (!modalBackground) { 14774 console.warn("Modal not found:", modalId); 14775 return; 14776 } 14777 14778 // Wait for modal to be visible, then scroll within modal content 14779 setTimeout(function() { 14780 requestAnimationFrame(function() { 14781 performModalScroll(anchorElement, hash, scrollAdjustment, modalBackground); 14782 }); 14783 }, 100); 14784 }; 14785 14786 /** 14787 * Routes to the appropriate scroll handler based on anchor type 14788 * @param {Element} anchorElement - The anchor element to scroll to 14789 * @param {string} hash - The hash to push to URL after scrolling 14790 */ 14791 var scrollToAnchor = function(anchorElement, hash) { 14792 var anchorType = anchorElement.dataset.anchorType || "default"; 14793 var scrollAdjustment = parseInt(anchorElement.dataset.scrollHeightAdjustment, 10) || 0; 14794 14795 if (anchorType === "default") { 14796 performScroll(anchorElement, hash, scrollAdjustment); 14797 } else if (anchorType === "accordion") {
14798 handleAccordionAnchor(anchorElement, hash, scrollAdjustment); 14799 } else if (anchorType === "tab") { 14800 handleTabAnchor(anchorElement, hash, scrollAdjustment); 14801 } else if (anchorType === "carousel") { 14802 handleCarouselAnchor(anchorElement, hash, scrollAdjustment); 14803 } else if (anchorType === "modal") { 14804 handleModalAnchor(anchorElement, hash, scrollAdjustment); 14805 } else { 14806 console.log("Anchor type:", anchorType, "Element:", anchorElement); 14807 } 14808 }; 14809 14810 /** 14811 * Check for stored hash on page load and scroll to anchor. 14812 * Uses the hash captured before it was stripped (to prevent native scroll), 14813 * and waits for window.load so layout is fully stable before scrolling. 14814 */ 14815 var handleInitialHash = function() { 14816 var hash = initialHash; 14817 if (!hash || hash === "") return; 14818 14819 var anchorElement = document.querySelector(".anchor-component" + hash); 14820 14821 if (!anchorElement) { 14822 // Hash doesn't target an anchor-component â restore it so other scripts 14823 // (generalmodal.js, accordion.js, promocarousel.js) can handle it via 14824 // their existing hashchange listeners. 14825 try { 14826 history.replaceState(null, null, hash); 14827 window.dispatchEvent(new Event('hashchange')); 14828 } catch(e) {} 14829 return; 14830 } 14831 14832 isInitialScrollInProgress = true; 14833 14834 var executeScroll = function() { 14835 requestAnimationFrame(function() { 14836 scrollToAnchor(anchorElement, hash); 14837 }); 14838 }; 14839 14840 if (document.readyState === 'complete') { 14841 executeScroll(); 14842 } else { 14843 window.addEventListener('load', executeScroll); 14844 } 14845 }; 14846 14847 /** 14848 * Handle click events on anchor links 14849 */ 14850 var handleAnchorClick = function(event) { 14851 var link = event.target.closest("a"); 14852 if (!link) return; 14853 14854 var hash = link.hash; 14855 if (!hash || hash === "") return; 14856 14857 var anchorElement = document.querySelector(".anchor-component" + hash); 14858 if (anchorElement) { 14859 event.preventDefault(); 14860 scrollToAnchor(anchorElement, hash); 14861 return; 14862 } 14863 14864 if (hasModalAPI() && window.generalModalAPI.findModalByHash(hash)) { 14865 event.preventDefault(); 14866 // pushState rather than assigning location.hash: the modal's 14867 // .modal-container carries id="anchorId", so a real fragment 14868 // navigation would make the browser scroll that target into view 14869 // and snap the page to the top. 14870 if (window.location.hash !== hash) { 14871 window.history.pushState(null, null, hash); 14872 } 14873 window.generalModalAPI.switchTo(hash); 14874 return; 14875 } 14876 }; 14877 14878 // Initialize on DOMContentLoaded 14879 document.addEventListener("DOMContentLoaded", function() { 14880 document.addEventListener("click", handleAnchorClick); 14881 handleInitialHash(); 14882 }); 14883})(); 14884 14885$(function () { 14886 $('.alert-banner__close.icon-x').on('click touchstart', function () { 14887 var ultimateParent = $(this).parent().parent().parent().parent().parent(); 14888 createAlertCookie(ultimateParent.attr('data-modified-date')); 14889 if ($(".alert.aem-GridColumn").length > 0) { 14890 $(".alert.aem-GridColumn")[0].innerHTML = ""; 14891 } else { 14892 $(".alert.section")[0].innerHTML = ""; 14893 } 14894 14895}); 14896 14897 $(document).ready(function () { 14898 var count = 0; 14899 $('.alert-banner-background-image-wrapper').each(function () { 14900 var title = 'alert' + count; 14901 count++; 14902 if (alertCookieExists($(this).attr('data-modified-date'))) { 14903 if ($(".alert.aem-GridColumn").length > 0) { 14904 $(".alert.aem-GridColumn")[0].innerHTML = ""; 14905 } else { 14906 $(".alert.section")[0].innerHTML = ""; 14907 } 14908 } 14909 }); 14910 }); 14911}); 14912 14913function createAlertCookie(cookieID) { 14914 //expiration set for session and identified and based on alert modified time 14915 document.cookie = cookieID + '=' + cookieID + '; expires=' + 0 + ';'; 14916} 14917 14918function alertCookieExists(cookieID) { 14919 return document.cookie.indexOf(cookieID) > -1; 14920} 14921 14922$(function () { 14923 var videoIsLoaded = false;
14924 if ($('.adaptive-carousel').length > 0) { 14925 $('.adaptive-carousel.flexslider').each(function () { 14926 if ($(this).find('li.item').length >= 1) { 14927 var intervalInt = parseInt($(this).attr('data-interval')), 14928 interval = intervalInt ? intervalInt : 3000, 14929 autoplay = $(this).attr('data-autoplay') == 'false' ? false : true; 14930 14931 var parent = $(this).parents('[data-target="true"]'); 14932 var that = this; 14933 if (parent.length > 0) { 14934 var targetInitialized = setInterval( 14935 function () { 14936 createSlides(interval, autoplay, that); 14937 clearInterval(targetInitialized); 14938 }, 14939 1000, 14940 parent 14941 ); 14942 } else { 14943 createSlides(interval, autoplay, this); 14944 } 14945 } 14946 }); 14947 14948 $(".adaptive-carousel .item").each(function() { 14949 if ($(this).data("url")) { 14950 var copy = $(this).find(".copy"); 14951 if (copy.length) { 14952 copy.wrap("<a href='" + $(this).data("url") + "' class='slide-item--anchor'></a>"); 14953 } 14954 } 14955 }); 14956 14957 $(window).on('resize load', function () { 14958 // when window loads or resizes, show all items and set their heights/margins 14959 setAdaptiveSlideHeights(); 14960 }); 14961 14962 $(document).ready(function () { 14963 var copy = $(this).find('.slides .item, .slides .copy'); 14964 setTimeout(function () { 14965 copy.addClass('slide--animate'); 14966 }, 1000); 14967 loadVideo(); 14968 if ($('.slides.cta').length > 0) { 14969 $('.adh-slide--btn').removeClass('btn'); 14970 $('.adh-slide--btn').addClass('cta'); 14971 } 14972 }); 14973 } 14974 14975 function createSlides(interval, autoplay, element) { 14976 $(element).flexslider({ 14977 pauseOnHover: true, 14978 controlNav: true, 14979 directionNav: false, 14980 slideshow: autoplay, 14981 slideshowSpeed: interval, 14982 14983 start: function (slider) { 14984 // when slider initializes, show all items 14985 slider.find('.item').addClass('initialized-item'); 14986 setAdaptiveSlideHeights(); 14987 $(element).data('goToSlide', function(index) { 14988 slider.flexAnimate(index); 14989 }); 14990 }, 14991 }); 14992 } 14993 14994 /** 14995 * Perform scroll animation using GSAP 14996 * @param {number} scrollPosition - Target scroll position 14997 */ 14998 function performScroll(scrollPosition) { 14999 console.log('performScroll', scrollPosition); 15000 var currentPosition = window.pageYOffset; 15001 var distance = Math.abs(scrollPosition - currentPosition); 15002 var pixelsPerSecond = 1200; 15003 var duration = distance / pixelsPerSecond; 15004 duration = Math.max(0.3, Math.min(1.5, duration)); 15005 var hash = window.location.hash; 15006 15007 gsap.to(window, { 15008 duration: duration, 15009 scrollTo: { 15010 y: scrollPosition, 15011 autoKill: false 15012 }, 15013 ease: "power2.out", 15014 onComplete: function() { 15015 // Ensure hash is preserved after scroll completes 15016 if (hash && window.location.hash !== hash) { 15017 window.history.replaceState(null, null, hash); 15018 } 15019 } 15020 }); 15021 } 15022 15023 /** 15024 * Handle hash-based navigation to slides 15025 * Navigates to a slide with matching anchor ID and scrolls to it 15026 * @param {string} hash - Optional hash to navigate to (defaults to window.location.hash) 15027 */ 15028 function handleHashNavigation(hash) { 15029 hash = hash || window.location.hash; 15030 console.log('hash', hash); 15031 if (!hash || hash === "") return; 15032 15033 15034 // Find slide with matching ID 15035 var targetSlide = document.querySelector('.adaptive-carousel .item' + hash); 15036 if (!targetSlide) return; 15037 15038 // Find the parent carousel 15039 var carousel = targetSlide.closest('.adaptive-carousel.flexslider'); 15040 if (!carousel) return; 15041 15042 // Calculate scroll position 15043 var scrollAdjustment = parseInt(targetSlide.dataset.scrollAdjustment, 10) || 0; 15044 var scrollPosition = carousel.getBoundingClientRect().top + window.pageYOffset + scrollAdjustment; 15045 15046 // Check if slide is already active (visible) 15047 var isAlreadyActive = targetSlide.classList.contains('flex-active-slide'); 15048 console.log('isAlreadyActive', isAlreadyActive); 15049 15050 if (isAlreadyActive) { 15051 // Already visible - scroll immediately 15052 performScroll(scrollPosition); 15053 } else { 15054 // Get slide index
15055 var slides = $(carousel).find('.slides > li.item'); 15056 var slideIndex = slides.index(targetSlide); 15057 15058 if (slideIndex > -1) { 15059 // Call our custom method 15060 var goToSlide = $(carousel).data('goToSlide'); 15061 if (goToSlide) { 15062 goToSlide(slideIndex); 15063 } 15064 15065 setTimeout(function() { 15066 performScroll(scrollPosition); 15067 }, 400); 15068 } 15069 } 15070} 15071 15072 // Intercept clicks on hash links that match adaptive carousel slide IDs 15073 $(document).on('click', 'a[href^="#"]', function(e) { 15074 var href = $(this).attr('href'); 15075 if (!href || href === '#') return; 15076 15077 // Only intercept if hash matches an adaptive carousel slide ID 15078 var targetSlide = document.querySelector('.adaptive-carousel .item' + href); 15079 if (!targetSlide) return; 15080 15081 e.preventDefault(); 15082 window.history.pushState(null, null, href); 15083 handleHashNavigation(href); 15084 }); 15085 15086 // Handle browser back/forward navigation 15087 window.addEventListener('hashchange', function() { 15088 handleHashNavigation(); 15089 }); 15090 15091 // Handle initial page load with hash 15092 $(window).on('load', function() { 15093 setTimeout(function() { 15094 handleHashNavigation(); 15095 }, 600); 15096 }); 15097 15098 function loadVideo() { 15099 if ( !videoIsLoaded) { 15100 var sources = document.querySelectorAll('.slide--video source'); 15101 // Define the video object this source is contained inside 15102 var video = document.querySelectorAll('.slide--video'); 15103 for (var i = 0; i < sources.length; i++) { 15104 sources[i].setAttribute('src', sources[i].getAttribute('data-src')); 15105 } 15106 // If for some reason we do want to load the video after, for desktop as opposed to mobile (I'd imagine), use videojs API to load 15107 if (video != null) { 15108 for (var i = 0; i < video.length; i++) { 15109 video[i].load(); 15110 } 15111 } 15112 videoIsLoaded = true; 15113 } 15114 } 15115 15116 function setAdaptiveSlideHeights() { 15117 $('.adaptive-carousel').removeAttr('style'); 15118 if ($(window).outerWidth() >= 992) { 15119 //var objectFit = new ObjectFitPolyfill('.adaptive-carousel video'); 15120 15121 // on desktop, remove inline styles that may have been set on mobile 15122 $('.adaptive-carousel.mobile-text-below .item').css( 15123 'margin-bottom', 15124 '0' 15125 ); 15126 $('.adaptive-carousel .item, .adaptive-carousel .copy').css( 15127 'height', 15128 'auto' 15129 ); 15130 // for each carousel, find the tallest item and update all item heights accordingly 15131 $('.adaptive-carousel').each(function () { 15132 var maxSlideHeight = 0; 15133 $(this) 15134 .find('.item') 15135 .each(function () { 15136 var slideHeight = $(this).outerHeight(); 15137 if (maxSlideHeight < slideHeight) { 15138 maxSlideHeight = slideHeight; 15139 } 15140 }); 15141 $(this); 15142 var copy = $(this).find('.slides .item, .slides .copy'); 15143 copy.css('height', maxSlideHeight); 15144 }); 15145 } else { 15146 // on mobile, let aspect ratio (padding-top) control image height 15147 $('.adaptive-carousel .item, .adaptive-carousel .copy').css( 15148 'height', 15149 'auto' 15150 ); 15151 $('.adaptive-carousel.mobile-text-below .item').css('height', '0px'); 15152 15153 $('.adaptive-carousel').each(function () { 15154 var maxCopyHeight = 0, 15155 maxSlideHeight = 0; 15156 15157 $(this) 15158 .find('.copy') 15159 .each(function () { 15160 var copyHeight = $(this).outerHeight(); 15161 if (maxCopyHeight < copyHeight) { 15162 maxCopyHeight = copyHeight; 15163 } 15164 }); 15165 15166 $(this) 15167 .find('.item') 15168 .each(function () { 15169 var slideHeight = $(this).outerHeight(); 15170 if (maxSlideHeight < slideHeight) { 15171 maxSlideHeight = slideHeight; 15172 } 15173 }); 15174 if ($(this).hasClass('mobile-text-below')) { 15175 // update all item margin-bottoms accordingly 15176 var ratio = parseInt( 15177 $(this).find('.slides .item').css('padding-top') 15178 ); 15179 ratioHeight = ratio + 16 + 'px'; 15180 $(this).find('.slides .item').css('margin-bottom', maxCopyHeight); 15181 $(this).find('.flex-control-nav').css('margin-top', ratioHeight); 15182 } else { 15183 // update all item heights accordingly 15184 15185 var copy = $(this).find('.slides .item, .slides .copy'); 15186 } 15187 }); 15188 } 15189 } 15190}); 15191 15192$(function(){ 15193 15194 // A General Modal authored inside accordion content matches the selectors 15195 // below, so without this guard opening the modal would also animate the page. 15196 function targetsGeneralModal() { 15197 return !!(window.generalModalAPI && window.generalModalAPI.findModalByHash(window.location.hash)); 15198 } 15199 15200 $('document').ready(function(){ 15201 if($(window.location.hash).closest('.accordion__content').length > 0) 15202 { 15203 if (document.querySelector('.anchor-component' + window.location.hash)) return; 15204 if (targetsGeneralModal()) return; 15205 $(window.location.hash).closest('.accordion__content').prev().trigger('click') 15206 $('html,body').animate({scrollTop: $(window.location.hash).closest('.accordion__content').prev().offset().top - 150},'slow'); 15207 }
15208 $( window ).on( 'hashchange', function( e ) { 15209 if($(window.location.hash).closest('.accordion__content').length > 0) 15210 { 15211 if (document.querySelector('.anchor-component' + window.location.hash)) return; 15212 if (targetsGeneralModal()) return; 15213 $(window.location.hash).closest('.accordion__content').prev().trigger('click') 15214 $('html,body').animate({scrollTop: $(window.location.hash).closest('.accordion__content').prev().offset().top - 150},'slow'); 15215 } 15216 } ); 15217 }); 15218 $('body').on('click touch', '.accordion__title', function(){ 15219 var $this = $(this); 15220 var $item = $this.parent('.accordion__item'); 15221 15222 if ( $item.hasClass('is-active') ) { 15223 $this.removeClass('is-active'); 15224 $this.next('.accordion__content').slideUp(400, function(){ 15225 $item.removeClass('is-active'); 15226 }); 15227 } else { 15228 $this.addClass('is-active'); 15229 var $gallery = $this.parent().find('.gallery'); 15230 $gallery.hide(); 15231 $this.next().slideDown(400, function(){ 15232 $item.addClass('is-active'); 15233 $gallery.show(); 15234 updateGallery($gallery); 15235 15236 15237 }); 15238 } 15239 15240 }); 15241 function updateGallery($gallery) { 15242 15243 $gallery.each(function(){ 15244 console.log("test") 15245 var galleryTemplate = $(this).data("galleryTemplate"); 15246 var autoPlay = $(this).data("galleryAutoplay"); 15247 var id = $(this).attr("id"); 15248 console.log(id) 15249 var galleryEle = '#'+id+' .gallery-top'; 15250 var thumbnailEle ='#'+id+' .gallery-thumbs'; 15251 var paginationEle = '#'+id+ ' .swiper-pagination'; 15252 var countSlides = $(galleryEle + ' .swiper-slide').length; 15253 var default_autoplay = 8000; 15254 var params = { 15255 keyboardControl: true, 15256 navigation: { 15257 nextEl: '.gallery-button-next', 15258 prevEl: '.gallery-button-prev', 15259 }, 15260 loop: (countSlides >= 6), 15261 loopedSlides: 7, 15262 preloadImages: false, 15263 autoHeight: true, 15264 lazyLoading: true, 15265 lazyLoadingInPrevNext: true, 15266 lazyLoadingOnTransitionStart: true, 15267 lazyLoadingInPrevNextAmount: 2, 15268 pagination:{ 15269 el: paginationEle, 15270 type: 'fraction' 15271 }, 15272 a11y: true 15273 }; 15274 if(autoPlay){ 15275 params.autoplay={ 15276 delay: default_autoplay, 15277 }; 15278 } 15279 15280 var thumbnailConfig={ 15281 spaceBetween: 0, 15282 centeredSlides: true, 15283 slidesPerView: (countSlides <= 6) ? 5 : 6, 15284 touchRatio: 0.2, 15285 slideToClickedSlide: true, 15286 loop: (countSlides >= 6), // loop is false when fewer slides than slidesPerView 15287 loopedSlides: 7 15288 }; 15289 15290 // setup the gallery 15291 var galleryTop = new Swiper (galleryEle,params); 15292 if(galleryTemplate!=="nothumbnail"){ 15293 var galleryThumbs = new Swiper(thumbnailEle, thumbnailConfig); 15294 galleryTop.thumbs = galleryThumbs; 15295 galleryTop.controller.control =galleryThumbs; 15296 galleryThumbs.controller.control = galleryTop; 15297 15298 15299 } 15300 }); 15301 15302 } 15303 15304 15305}); 15306
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.