PageSourceSearch

https://cstudios.digital/assets/js/main.js?v=1790586577

js cstudios.digital collected 2026-10-06 13:38:05 UTC 32,324 bytes, 975 lines download raw bytes

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.