1import { headerInteractivity } from "./utils.js"; 2import { CountUp } from "./countUp.min.js"; 3 4document.addEventListener("DOMContentLoaded", () => { 5 // |----- 6 // | Global helper functions 7 // |----- 8 9 /** 10 * 11 * @param {HTMLElement} element 12 */ 13 window.toggleRotate = (element) => { 14 let currentState = 15 getComputedStyle(element).getPropertyValue("--state"); 16 17 let newState = currentState === "paused" ? "running" : "paused"; 18 element.style.setProperty("--state", newState); 19 }; 20 21 // CountUp defaults to an English comma; sk and hu group with a space. 22 const countUpSeparator = 23 document.documentElement.lang?.startsWith("en") ? "," : "Â "; 24 25 document.querySelectorAll("[data-countup-num]").forEach((e) => { 26 let number = e.dataset.countupNum; 27 let suffix = e.dataset.countupSuffix; 28 let prefix = e.dataset.countupPrefix; 29 let duration = e.dataset.countupDuration 30 ? parseFloat(e.dataset.countupDuration) 31 : 4; 32 new CountUp(e, number, { 33 prefix: prefix + " " ?? "", 34 suffix: suffix ?? "", 35 separator: countUpSeparator, 36 enableScrollSpy: true, 37 scrollSpyOnce: true, 38 duration: duration, 39 }); 40 }); 41 42 headerInteractivity(); 43 44 if (typeof Fancybox !== "undefined") { 45 Fancybox.bind("[data-fancybox]", { 46 // Your custom options 47 }); 48 } 49 50 // |----- 51 // | Splide sliders 52 // |----- 53 54 if (typeof Splide !== "undefined") { 55 const reviewsContainer = document.querySelector(".reviews-splide"); 56 if (reviewsContainer) { 57 const paddingAmount = "min(5%, 60px)"; 58 const reviewsSplide = new Splide(reviewsContainer, { 59 pagination: false, 60 arrows: false, 61 perPage: 5, 62 perMove: 1, 63 gap: "min(2%, 20px)", 64 padding: { 65 right: paddingAmount, 66 left: paddingAmount, 67 }, 68 drag: false, 69 breakpoints: { 70 1920: { 71 perPage: 4, 72 }, 73 1400: { 74 perPage: 3, 75 }, 76 992: { 77 perPage: 2, 78 drag: true, 79 }, 80 576: { 81 perPage: 1, 82 }, 83 }, 84 }); 85 86 const prevButton = reviewsContainer.querySelector(".slider-prev"); 87 if (prevButton) { 88 prevButton.addEventListener("click", () => { 89 reviewsSplide.go("<"); 90 }); 91 } 92 93 const nextButton = reviewsContainer.querySelector(".slider-next"); 94 if (nextButton) { 95 nextButton.addEventListener("click", () => { 96 reviewsSplide.go(">"); 97 }); 98 } 99 100 reviewsSplide.on("overflow", (isOverflow) => { 101 const controls = 102 reviewsContainer.querySelector(".buttons-group"); 103 104 if (controls) { 105 if (isOverflow) { 106 controls.style.removeProperty("display"); 107 } else { 108 controls.style.display = "none"; 109 } 110 } 111 }); 112 113 mountWhenNear(reviewsContainer, reviewsSplide); 114 115 reviewsSplide.on("mounted move", (newIndex, prevIndex) => { 116 const len = reviewsSplide.Components.Slides.getLength(); 117 118 let currentIndex = reviewsSplide.index; 119 prevButton.disabled = parseInt(currentIndex) === 0; 120 nextButton.disabled = 121 parseInt(currentIndex) === 122 len - reviewsSplide.options.perPage; 123 }); 124 125 reviewsContainer 126 .querySelector(".splide__track") 127 ?.classList.add("splide-track-padding"); 128 } 129 130 const awardsContainer = document.querySelectorAll(".default-splide"); 131 if (awardsContainer) { 132 awardsContainer.forEach((el) => { 133 const awardsSplide = new Splide(el, { 134 perPage: 5, 135 perMove: 1, 136 gap: "24px",
137 arrows: false, 138 pagination: false, 139 rewind: false, 140 breakpoints: { 141 768: { 142 perPage: 2, 143 }, 144 992: { 145 perPage: 3, 146 }, 147 1200: { 148 perPage: 4, 149 }, 150 }, 151 }); 152 153 // addArrowsWithProgress(el, awardsSplide); 154 155 const prevButton = el.querySelector(".slider-prev"); 156 const nextButton = el.querySelector(".slider-next"); 157 if (prevButton) { 158 prevButton.addEventListener("click", () => { 159 awardsSplide.go("<"); 160 }); 161 } 162 163 if (nextButton) { 164 nextButton.addEventListener("click", () => { 165 awardsSplide.go(">"); 166 }); 167 } 168 awardsSplide.on("mounted move updated resize", () => { 169 updateProgressBar(awardsSplide, el, prevButton, nextButton); 170 }); 171 172 awardsSplide.on("overflow", (isOverflow) => { 173 const controls = el.querySelector(".slider-controls"); 174 175 awardsSplide.options = { 176 ...awardsSplide.options, 177 drag: isOverflow, 178 }; 179 if (controls) { 180 if (isOverflow) { 181 controls.style.removeProperty("display"); 182 } else { 183 controls.style.display = "none"; 184 } 185 } 186 }); 187 188 mountWhenNear(el, awardsSplide); 189 }); 190 } 191 192 const container = document.querySelector(".case-studies-splide"); 193 if (container) { 194 let paddingAmount = "15%"; 195 const splide = new Splide(container, { 196 perPage: 3, 197 perMove: 1, 198 padding: { 199 right: paddingAmount, 200 }, 201 lazyLoad: "sequential", 202 gap: "24px", 203 arrows: false, 204 pagination: false, 205 rewind: false, 206 breakpoints: { 207 768: { 208 perPage: 1, 209 }, 210 1240: { 211 perPage: 2, 212 }, 213 }, 214 }); 215 216 splide.on("moved", (newIndex, prevIndex) => { 217 const len = splide.Components.Slides.getLength(); 218 219 if (len - splide.options.perPage === newIndex) { 220 splide.options = { 221 ...splide, 222 padding: { right: "0px", left: paddingAmount }, 223 }; 224 } else if (newIndex === 0) { 225 splide.options = { 226 ...splide, 227 padding: { right: paddingAmount, left: "0px" }, 228 }; 229 } 230 }); 231 232 const prevButton = container.querySelector(".slider-prev"); 233 prevButton.addEventListener("click", () => { 234 splide.go("<"); 235 }); 236 237 const nextButton = container.querySelector(".slider-next"); 238 nextButton.addEventListener("click", () => { 239 splide.go(">"); 240 }); 241 splide.on("mounted move refresh moved", () => 242 updateProgressBar(splide, container, prevButton, nextButton), 243 ); 244 245 splide.on("overflow", (isOverflow) => { 246 const controls = container.querySelector(".slider-controls"); 247 248 splide.options = { 249 ...splide.options, 250 drag: isOverflow, 251 }; 252 if (controls) { 253 if (isOverflow) { 254 controls.style.removeProperty("display"); 255 } else { 256 controls.style.display = "none"; 257 } 258 } 259 }); 260 261 mountWhenNear(container, splide); 262 263 window.caseStudies = { 264 splide: splide, 265 container: container, 266 prevButton, 267 nextButton, 268 }; 269 270 container 271 .querySelector(".splide__track") 272 ?.classList.add("splide-track-padding"); 273 } 274 275 let started = false;
276 277 const contactLandingContainer = document.querySelector( 278 "main.contact section.landing .splide", 279 ); 280 if (contactLandingContainer) { 281 const contactLandingSplide = new Splide(contactLandingContainer, { 282 perPage: 1, 283 perMove: 1, 284 height: "800px", 285 pagination: false, 286 arrows: false, 287 type: "loop", 288 autoplay: "pause", 289 interval: 3750, 290 speed: 850, 291 easing: "ease", 292 pauseOnHover: false, 293 drag: false, 294 breakpoints: { 295 768: { 296 height: "clamp(300px, 50vh, 500px)", 297 }, 298 }, 299 }); 300 301 contactLandingSplide.on("autoplay:play", () => { 302 setTimeout(() => { 303 contactLandingSplide.go(">"); 304 }, 500); 305 }); 306 contactLandingSplide.mount(); 307 } 308 309 const galleryDesktopContainer = 310 document.querySelector(".desktop-gallery"); 311 if (galleryDesktopContainer) { 312 const galleryDesktopSplide = new Splide(galleryDesktopContainer, { 313 perPage: 1, 314 perMove: 1, 315 rewind: true, 316 }); 317 318 galleryDesktopSplide.on("overflow", (isOverflow) => { 319 galleryDesktopSplide.options = { 320 ...galleryDesktopSplide.options, 321 drag: isOverflow, 322 arrows: isOverflow, 323 }; 324 }); 325 326 galleryDesktopSplide.mount(); 327 } 328 329 const dropdownContainer = document.querySelectorAll( 330 "section.dropdown:has(section.dropdown-splide)", 331 ); 332 if (dropdownContainer.length) { 333 dropdownContainer.forEach((cont) => { 334 const dropdownSplide = new Splide( 335 cont.querySelector("section.dropdown-splide"), 336 { 337 type: "fade", 338 perMove: 1, 339 perPage: 1, 340 // direction: 'ttb', 341 height: "100%", 342 cover: true, 343 arrows: false, 344 pagination: false, 345 }, 346 ); 347 348 cont.querySelectorAll(".row").forEach((row) => { 349 const rowGoto = row.dataset.splideGoto ?? ""; 350 if (rowGoto) { 351 row.querySelectorAll("button").forEach((btn) => { 352 btn.addEventListener("click", () => { 353 dropdownSplide.go(parseInt(rowGoto)); 354 }); 355 }); 356 } else { 357 console.log( 358 "Button doesn't have a data-splide-goto property", 359 ); 360 } 361 }); 362 363 dropdownSplide.mount(); 364 }); 365 } 366 367 const dropdownV2Container = document.querySelectorAll( 368 "section.scroll-dropdown:has(section.dropdown-splide)", 369 ); 370 if (dropdownV2Container.length) { 371 dropdownV2Container.forEach((cont) => { 372 const dropdownSplide = new Splide( 373 cont.querySelector("section.dropdown-splide"), 374 { 375 type: "fade", 376 perMove: 1, 377 perPage: 1, 378 // direction: 'ttb', 379 height: "100%", 380 cover: true, 381 arrows: false, 382 pagination: false, 383 }, 384 ); 385 window.scrollDropdownSplide = dropdownSplide; 386 387 dropdownSplide.mount(); 388 }); 389 } 390 391 const dropdownV2Text = document.querySelector( 392 "section.splide.content-mobile", 393 ); 394 if (dropdownV2Text) { 395 let maxh = 0; 396 const splidelist = dropdownV2Text.querySelector(".splide__list"); 397 splidelist.style.flexDirection = "column"; 398 399 // dropdownV2Text.style.display = 'block'; 400 dropdownV2Text.querySelectorAll(".desc").forEach((d) => { 401 maxh = Math.max(maxh, d.offsetHeight); 402 console.log(d.offsetHeight); 403 }); 404 // dropdownV2Text.style.removeProperty('display'); 405 splidelist.style.removeProperty("flex-direction"); 406 407 const ddt = new Splide(dropdownV2Text, { 408 perPage: 1, 409 perMove: 1, 410 direction: "ttb", 411 height: maxh + "px", 412 pagination: false,
413 arrows: false, 414 gap: "20px", 415 }); 416 417 const prevButton = dropdownV2Text.querySelector(".slider-prev"); 418 prevButton.addEventListener("click", () => { 419 ddt.go("<"); 420 }); 421 422 const nextButton = dropdownV2Text.querySelector(".slider-next"); 423 nextButton.addEventListener("click", () => { 424 ddt.go(">"); 425 }); 426 427 ddt.on("move mounted", (newIndex) => { 428 window.scrollDropdownSplide.go(newIndex); 429 updateProgressBar( 430 ddt, 431 dropdownV2Text, 432 prevButton, 433 nextButton, 434 "horizontal", 435 ); 436 }); 437 438 ddt.mount(); 439 } 440 441 const fullscreenSplide = document.querySelector( 442 "section.fullscreen-slider-splide", 443 ); 444 if (fullscreenSplide) { 445 const fss = new Splide(fullscreenSplide, { 446 perPage: 1, 447 perMove: 1, 448 pagination: false, 449 arrows: false, 450 gap: "20px", 451 speed: 650, 452 }); 453 454 const prevButton = fullscreenSplide 455 .closest(".fullscreen-slider") 456 .querySelector(".slider-prev"); 457 prevButton?.addEventListener("click", () => { 458 fss.go("<"); 459 }); 460 461 const nextButton = fullscreenSplide 462 .closest(".fullscreen-slider") 463 .querySelector(".slider-next"); 464 nextButton?.addEventListener("click", () => { 465 fss.go(">"); 466 }); 467 468 fss.on("move mounted", (newIndex) => { 469 updateProgressBar( 470 fss, 471 fullscreenSplide.closest(".fullscreen-slider"), 472 prevButton, 473 nextButton, 474 "vertical", 475 ); 476 }); 477 478 fss.mount(); 479 } 480 481 const referencesBlockSplide = document.querySelector( 482 "section.references-block-splide", 483 ); 484 if (referencesBlockSplide) { 485 const rbs = new Splide(referencesBlockSplide, { 486 perPage: 3, 487 perMove: 1, 488 pagination: false, 489 padding: { 490 right: "var(--right-margin)", 491 }, 492 arrows: false, 493 gap: "clamp(2rem, 1.5962rem + 1.7949vw, 3.75rem)", 494 speed: 650, 495 breakpoints: { 496 992: { 497 perPage: 2, 498 }, 499 552: { 500 perPage: 1, 501 }, 502 }, 503 }); 504 505 const prevButton = referencesBlockSplide 506 .closest(".inner") 507 ?.querySelector(".slider-prev"); 508 prevButton?.addEventListener("click", () => { 509 rbs.go("<"); 510 }); 511 512 const nextButton = referencesBlockSplide 513 .closest(".inner") 514 ?.querySelector(".slider-next"); 515 nextButton?.addEventListener("click", () => { 516 rbs.go(">"); 517 }); 518 519 rbs.on("move mounted", (newIndex) => { 520 updateProgressBar( 521 rbs, 522 referencesBlockSplide.closest("section.references-block"), 523 prevButton, 524 nextButton, 525 "vertical", 526 ); 527 }); 528 529 rbs.mount(); 530 } 531 532 // Splide is defined closing 533 } 534 535 function fitLine(el, maxWidth, maxHeight) { 536 let size = maxHeight; 537 el.style.fontSize = size + "px"; 538 while ( 539 size > 8 && 540 (el.scrollWidth > maxWidth || el.scrollHeight > maxHeight) 541 ) { 542 size -= 1; 543 el.style.fontSize = size + "px"; 544 } 545 return size; 546 } 547 548 function fitButton(btn) { 549 const lines = btn.querySelectorAll(".eyebrow-line"); 550 const padding = 0.65; 551 const maxWidth = btn.clientWidth * padding; 552 const maxHeight = btn.clientHeight * padding; 553 554 if (lines.length === 1) { 555 fitLine(lines[0], maxWidth, maxHeight); 556 return; 557 } 558 559 const sizes = Array.from(lines).map((line) => 560 fitLine(line, maxWidth, maxHeight * 0.6), 561 ); 562 563 const total = sizes.reduce((a, b) => a + b, 0); 564 if (total > maxHeight) { 565 const scale = maxHeight / total;
566 lines.forEach((line, i) => { 567 line.style.fontSize = sizes[i] * scale + "px"; 568 }); 569 } 570 } 571 572 function fitAllEyebrowButtons() { 573 document.querySelectorAll("button.custom-eyebrow").forEach(fitButton); 574 } 575 576 function debounce(fn, delay) { 577 let timer; 578 return function (...args) { 579 clearTimeout(timer); 580 timer = setTimeout(() => fn.apply(this, args), delay); 581 }; 582 } 583 584 window.addEventListener("load", fitAllEyebrowButtons); 585 window.addEventListener("resize", debounce(fitAllEyebrowButtons, 150)); 586 587 // Wraps every whitespace-separated word in a clip wrapper + .animate-item. 588 // Unlike anime's splitText, inline markup inside a word is kept in the same 589 // word, so `kt<span class="orange-highlight">o</span>ré` animates as one. 590 function splitWordsKeepingMarkup(root) { 591 const tokens = []; 592 const collect = (node, ancestors) => { 593 if (node.nodeType === Node.ELEMENT_NODE) { 594 node.childNodes.forEach((child) => 595 collect(child, [...ancestors, node]), 596 ); 597 return; 598 } 599 if (node.nodeType !== Node.TEXT_NODE) return; 600 601 node.textContent.split(/(\s+)/).forEach((part) => { 602 if (!part) return; 603 if (/^\s+$/.test(part)) { 604 tokens.push(part); 605 return; 606 } 607 tokens.push( 608 ancestors.reduceRight((child, el) => { 609 const clone = el.cloneNode(false); 610 clone.append(child); 611 return clone; 612 }, document.createTextNode(part)), 613 ); 614 }); 615 }; 616 root.childNodes.forEach((child) => collect(child, [])); 617 618 root.replaceChildren(); 619 const items = []; 620 let item = null; 621 tokens.forEach((token) => { 622 if (typeof token === "string") { 623 root.append(token); 624 item = null; 625 return; 626 } 627 if (!item) { 628 const clip = document.createElement("span"); 629 clip.className = "animate-word"; 630 clip.style.cssText = "overflow: clip; display: inline-block;"; 631 item = document.createElement("span"); 632 item.className = "animate-item"; 633 item.style.display = "inline-block"; 634 clip.append(item); 635 root.append(clip); 636 items.push(item); 637 } 638 item.append(token); 639 }); 640 return items; 641 } 642 643 const listOfAnimatedElementSelectors = [ 644 ".about-animate-inside", 645 ".animate-inside", 646 ]; 647 648 let hasToAnimate = false; 649 listOfAnimatedElementSelectors.forEach((sel) => { 650 if (document.querySelectorAll(sel).length) { 651 hasToAnimate = true; 652 } 653 }); 654 655 // hasToAnimate = false; 656 if (hasToAnimate) { 657 const { animate, createTimeline, stagger } = anime; 658 const aboutHeadings = document.querySelectorAll( 659 ".about-animate-inside", 660 ); 661 662 const tl = createTimeline({ 663 // loop: true, 664 defaults: { ease: "inOut(3)", duration: 650 }, 665 }); 666 if (document.querySelector(".animate-inside")) { 667 const words = [ 668 ...document.querySelectorAll(".animate-inside"), 669 ].flatMap(splitWordsKeepingMarkup); 670 tl.add( 671 words, 672 { 673 y: ["calc(100% + 8px)", "0%"], 674 }, 675 stagger(120), 676 ); 677 } 678 679 tl.init(); 680 } 681 682 animateScroll(".can-fade-up", "animation-fade-up"); 683 animateScroll(".can-roll-down", "animation-roll-down"); 684 highlightScroll(".scroll-dropdown .content .desc", "highlighted"); 685 686 const bzzzInputs = document.querySelectorAll('input[name="bzzz"]'); 687 if (bzzzInputs.length) { 688 setTimeout(() => {
689 bzzzInputs.forEach((el) => (el.value = "bzzz")); 690 }, 1000); 691 } 692 693 const dialog = document.querySelector("dialog#price-offer"); 694 const opensPriceOfferDialog = document.querySelectorAll( 695 "button.opens-price-dialog", 696 ); 697 opensPriceOfferDialog.forEach((btn) => { 698 btn.addEventListener("click", () => { 699 if (dialog) { 700 dialog.showModal(); 701 } 702 }); 703 }); 704 705 const consultationDialog = document.querySelector( 706 "dialog#consultation-reservation", 707 ); 708 const opensConsultationDialog = document.querySelectorAll( 709 "button.opens-consultation-reservation", 710 ); 711 712 opensConsultationDialog.forEach((btn) => { 713 btn.addEventListener("click", () => { 714 if (consultationDialog) { 715 consultationDialog.showModal(); 716 } 717 }); 718 }); 719 720 const googleBusinessDialog = document.querySelector( 721 "#google-business-profile", 722 ); 723 const opensGoogleBusinessDialog = document.querySelectorAll( 724 "button.opens-google-business-dialog", 725 ); 726 727 opensGoogleBusinessDialog.forEach((btn) => { 728 btn.addEventListener("click", () => { 729 if (googleBusinessDialog) { 730 googleBusinessDialog.showModal(); 731 } 732 }); 733 }); 734 735 document.querySelectorAll(".close-dialog").forEach((el) => { 736 el.addEventListener("click", (e) => { 737 e.preventDefault(); 738 el?.closest("dialog")?.close(); 739 }); 740 }); 741 742 if (dialog) { 743 let mouseDownOutside = false;
744 745 dialog.addEventListener("mousedown", function (event) { 746 const rect = dialog.getBoundingClientRect(); 747 const isInDialog = 748 rect.top <= event.clientY && 749 event.clientY <= rect.top + rect.height && 750 rect.left <= event.clientX && 751 event.clientX <= rect.left + rect.width; 752 mouseDownOutside = !isInDialog; 753 }); 754 755 dialog.addEventListener("mouseup", function (event) { 756 const rect = dialog.getBoundingClientRect(); 757 const isInDialog = 758 rect.top <= event.clientY && 759 event.clientY <= rect.top + rect.height && 760 rect.left <= event.clientX && 761 event.clientX <= rect.left + rect.width; 762 if (mouseDownOutside && !isInDialog) { 763 dialog.close(); 764 } 765 mouseDownOutside = false; 766 }); 767 } 768}); 769 770// One IntersectionObserver per selector instead of an anime onScroll per 771// element: those measure every target on the main thread (a forced layout 772// each), where an observer is computed by the browser without any. 773 774// Highlighted while the element crosses a line 47% down the viewport. The 775// root margins shrink the viewport to that zero-height line. 776function highlightScroll(selector, addClass) { 777 const observer = new IntersectionObserver( 778 (entries) => { 779 entries.forEach(({ isIntersecting, target }) => { 780 if (!isIntersecting) { 781 target.classList.remove(addClass); 782 return; 783 } 784 target.classList.add(addClass); 785 window.scrollDropdownSplide?.go(parseInt(target.dataset.index)); 786 }); 787 }, 788 { rootMargin: "-47% 0px -53% 0px" }, 789 ); 790 document.querySelectorAll(selector).forEach((e) => observer.observe(e)); 791} 792 793// Swaps `selector` for `addClass` once the element's top passes 95% of the 794// viewport height, then stops watching it. 795function animateScroll(selector, addClass) { 796 const observer = new IntersectionObserver( 797 (entries) => { 798 entries.forEach(({ isIntersecting, target }) => { 799 if (!isIntersecting) return; 800 target.classList.add(addClass); 801 target.classList.remove(selector.replace(".", "")); 802 observer.unobserve(target); 803 }); 804 }, 805 { rootMargin: "0px 0px -5% 0px" }, 806 ); 807 document.querySelectorAll(selector).forEach((e) => observer.observe(e)); 808} 809 810// Mounts a slider once it is within a screen of the viewport. Mounting 811// measures every slide, and doing that for sliders far below the fold was the 812// largest forced reflow on page load. Splide keeps an unmounted slider hidden, 813// so the margin has to be generous enough that it is ready before it is seen. 814function mountWhenNear(el, splide) { 815 const observer = new IntersectionObserver( 816 (entries) => { 817 if (!entries[0].isIntersecting) return; 818 observer.disconnect(); 819 splide.mount(); 820 }, 821 { rootMargin: "100% 0px" }, 822 ); 823 observer.observe(el); 824} 825 826function updateProgressBar( 827 splide, 828 container, 829 prevButton, 830 nextButton, 831 orientation = "vertical", 832) { 833 let end = splide.Components.Controller.getEnd() + 1; 834 let rate = Math.min((splide.index + 1) / end, 1); 835 let bar = container.querySelector(".slider-progress-bar"); 836 const newVal = String(100 * rate); 837 let currentIndex = splide.index; 838 prevButton.disabled = parseInt(currentIndex) === 0; 839 nextButton.disabled = parseInt(currentIndex) === end - 1; 840 841 if (orientation === "vertical") { 842 if (!isNaN(rate)) { 843 bar.style.width = String(100 * rate) + "%"; 844 } 845 } else if (orientation === "horizontal") { 846 if (!isNaN(rate)) { 847 bar.style.height = String(100 * rate) + "%"; 848 } 849 } else { 850 return 0; 851 } 852} 853 854/** 855 * 856 * @param {Splide} splide 857 */ 858window.refreshSplide = (splide) => { 859 splide.refresh(); 860}; 861 862window.updateProgressBar = updateProgressBar; 863 864window.allSlidesVisible = (containerCandidate) => { 865 const container = document.querySelector(containerCandidate); 866 if (container) { 867 const visibleContainer = container.querySelector("ul.splide__list"); 868 const hiddenContainer = container.querySelector(".hidden-slides"); 869 const hiddenSlides = hiddenContainer.querySelectorAll(".project-card");
870 hiddenSlides.forEach((e) => { 871 visibleContainer.appendChild(e); 872 // hiddenContainer.removeChild(e); 873 }); 874 } 875}; 876 877/** 878 * 879 * @param {string} tag 880 * @param {HTMLElement} containerCandidate 881 */ 882window.filterSlides = (containerCandidate, tag) => { 883 const container = document.querySelector(containerCandidate); 884 if (container) { 885 const visibleContainer = container.querySelector("ul.splide__list"); 886 const hiddenContainer = container.querySelector(".hidden-slides"); 887 888 const visibleSlides = 889 visibleContainer.querySelectorAll(".project-card"); 890 visibleSlides.forEach((e) => { 891 if (!e.dataset.tags.split(",").includes(tag)) { 892 hiddenContainer.appendChild(e); 893 } 894 }); 895 896 const hiddenSlides = hiddenContainer.querySelectorAll(".project-card"); 897 hiddenSlides.forEach((e) => { 898 if (e.dataset.tags.split(",").includes(tag)) { 899 visibleContainer.appendChild(e); 900 } 901 }); 902 903 window.refreshSplide(window.caseStudies.splide); 904 } 905}; 906 907/** 908 * 909 * @param {string} filter 910 */ 911window.changeFilter = (filter) => { 912 if (filter !== "") { 913 window.history.pushState({}, "", `?filter=${filter}`); 914 } else { 915 const url = new URL(window.location); 916 url.searchParams.delete("filter"); 917 window.history.replaceState({}, "", url); 918 } 919}; 920 921// countdown 922 923if (document.querySelector(".countdown-page")) { 924 (function () { 925 const second = 1000, 926 minute = second * 60, 927 hour = minute * 60, 928 day = hour * 24; 929 930 //I'm adding this section so I don't have to keep updating this pen every year :-) 931 //remove this if you don't need it 932 let today = new Date(), 933 dd = String(today.getDate()).padStart(2, "0"), 934 mm = String(today.getMonth() + 1).padStart(2, "0"), 935 yyyy = today.getFullYear(), 936 nextYear = yyyy + 1, 937 dayMonth = "11/26/", 938 birthday = dayMonth + yyyy; 939 940 today = mm + "/" + dd + "/" + yyyy; 941 if (today > birthday) { 942 birthday = dayMonth + nextYear; 943 } 944 //end 945 946 const countDown = new Date(birthday).getTime(), 947 x = setInterval(function () { 948 const now = new Date().getTime(), 949 distance = countDown - now; 950 951 ((document.getElementById("days").innerText = Math.floor( 952 distance / day, 953 )), 954 (document.getElementById("hours").innerText = Math.floor( 955 (distance % day) / hour, 956 )), 957 (document.getElementById("minutes").innerText = Math.floor( 958 (distance % hour) / minute, 959 )), 960 (document.getElementById("seconds").innerText = Math.floor( 961 (distance % minute) / second, 962 ))); 963 964 //do something later when date is reached 965 if (distance < 0) { 966 document.getElementById("headline").innerText = 967 "Today is the Day!"; 968 document.getElementById("countdown").style.display = "none"; 969 document.getElementById("content").style.display = "block"; 970 clearInterval(x); 971 } 972 //seconds 973 }, 0); 974 })(); 975}
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.