PageSourceSearch

https://printerval.com/site-v2/assets/js/hello-bar.js?v=20261002033852

js printerval.com collected 2026-10-02 03:54:22 UTC 6,696 bytes, 187 lines download raw bytes

1document.addEventListener("DOMContentLoaded", function () {
2    // Pages carry different carousel libs: most load swiper.min.js, some only
3    // get Embla (bundled with the product scripts). Drive the bar with whichever
4    // one is on the page — Swiper first, Embla otherwise.
5    const bar = document.querySelector(".hello-bar-item-left");
6
7    // The bar used to start rotating ~5s after DOMContentLoaded, putting its first
8    // slide transition at ~6s — which was the last visual change in the entire
9    // Lighthouse trace, and therefore what set Speed Index (6.5s reported, on a
10    // page whose paint actually finishes at 374ms observed). A promo bar that
11    // starts cycling once the visitor is engaged loses nothing; one that cycles
12    // while the page is still loading costs a measurable amount, both in the
13    // metric and in frames the page needs elsewhere.
14    //
15    // So: never before load, then on the first sign of engagement. The fallback
16    // is for visitors who never touch the page — long enough to stay clear of
17    // the load window, short enough that they still see the other slides.
18    const AUTOPLAY_FALLBACK_MS = 15000;
19    const ENGAGEMENT_EVENTS = ["pointerdown", "touchstart", "keydown", "wheel", "scroll"];
20
21    function whenReadyToAnimate(callback) {
22        let fired = false;
23        const run = () => {
24            if (fired) return;
25            fired = true;
26            ENGAGEMENT_EVENTS.forEach((name) =>
27                window.removeEventListener(name, run, { passive: true, capture: true })
28            );
29            callback();
30        };
31        const armTriggers = () => {
32            ENGAGEMENT_EVENTS.forEach((name) =>
33                window.addEventListener(name, run, { passive: true, capture: true, once: true })
34            );
35            setTimeout(run, AUTOPLAY_FALLBACK_MS);
36        };
37        if (document.readyState === "complete") armTriggers();
38        else window.addEventListener("load", armTriggers);
39    }
40
41    function initHelloBarSwiper(bar) {
42        const swiper = new Swiper(bar, {
43            loop: true,
44            fadeEffect: { crossFade: true },
45            autoplay: {
46                delay: helloBarSlideDelay,
47                disableOnInteraction: false,
48            },
49        });
50
51        bar.addEventListener("mouseenter", () => swiper.autoplay.stop());
52        bar.addEventListener("mouseleave", () => swiper.autoplay.start());
53    }
54
55    // Embla core has no autoplay plugin, so the timer is ours.
56    function initHelloBarEmbla(bar) {
57        const embla = EmblaCarousel(bar, {
58            container: ".swiper-wrapper",
59            slides: ".swiper-slide",
60            loop: true,
61            align: "start",
62            containScroll: false,
63        });
64
65        let timer = null;
66
67        const stopAutoplay = () => {
68            if (timer) {
69                clearInterval(timer);
70                timer = null;
71            }
72        };
73
74        const startAutoplay = () => {
75            stopAutoplay();
76            if (helloBarSlideDelay <= 0) return;
77            timer = setInterval(() => {
78                if (!document.hidden) embla.scrollNext();
79            }, helloBarSlideDelay);
80        };
81
82        bar.addEventListener("mouseenter", stopAutoplay);
83        bar.addEventListener("mouseleave", startAutoplay);
84        // Swiper's disableOnInteraction: false — a drag pauses, release resumes.
85        embla.on("pointerDown", stopAutoplay);
86        embla.on("pointerUp", startAutoplay);
87
88        whenReadyToAnimate(startAutoplay);
89    }
90
91    // Bundles that define either lib may load async, i.e. after DOMContentLoaded,
92    // so keep looking for a short while. If neither shows up the bar just sits on
93    // its first slide instead of throwing.
94    if (bar && bar.querySelectorAll(".swiper-slide").length > 1) {
95        let initialized = false;
96
97        function initWhenLibReady() {
98            if (initialized) return;
99
100            if (typeof Swiper !== "undefined") {
101                initialized = true;
102                initHelloBarSwiper(bar);
103                return;
104            }
105
106            if (typeof EmblaCarousel !== "undefined") {
107                initialized = true;
108                initHelloBarEmbla(bar);
109                return;
110            }
111
112            setTimeout(initWhenLibReady, 100);
113        }
114        whenReadyToAnimate(initWhenLibReady);
115    }
116
117    const openBtn  = document.getElementById('js-get-printerval-app');
118    const popup    = document.querySelector('.hello-app-popup');
119    const closeBtn = document.querySelector('.hello-app-popup-button');
120    const closeBg  = document.querySelector('.hello-app-popup-bg');
121
122    if (openBtn) {
123        openBtn.addEventListener('click', function () {
124            popup?.classList.add('is-active');
125        });
126    }
127
128    function closePopup() {
129        popup?.classList.remove('is-active');
130    }
131
132    closeBtn?.addEventListener('click', closePopup);
133    closeBg?.addEventListener('click', closePopup);
134
135    document.addEventListener("click", function (event) {
136        const wrapper = event.target.closest(".copy-wrapper");
137        if (!wrapper) return;
138
139        const textToCopy = wrapper.dataset.copy;
140        if (!textToCopy) return;
141
142        function showCopied(wrapper) {
143            const oldTooltip = wrapper.querySelector(".hello-bar-tooltip");
144            if (oldTooltip) oldTooltip.remove();
145
146            const tooltip = document.createElement("span");
147            tooltip.className = "hello-bar-tooltip";
148            tooltip.textContent = "{{ __('Copied') }}";
149
150            wrapper.appendChild(tooltip);
151
152            setTimeout(() => {
153                tooltip.remove();
154            }, 1500);
155        }
156
157        // Copy API mới
158        if (navigator.clipboard && navigator.clipboard.writeText) {
159            navigator.clipboard.writeText(textToCopy)
160                .then(() => {
161                    showCopied(wrapper);
162                })
163                .catch(err => {
164                    console.error("Clipboard error:", err);
165                });
166        } else {
167            // Copy fallback cũ
168            const textarea = document.createElement("textarea");
169            textarea.value = textToCopy;
170            textarea.style.position = "fixed";
171            textarea.style.top = "-9999px";
172            textarea.style.left = "-9999px";
173            document.body.appendChild(textarea);
174            textarea.select();
175
176            try {
177                document.execCommand("copy");
178                showCopied(wrapper);
179            } catch (err) {
180                console.error("Fallback copy failed:", err);
181            }
182
183            document.body.removeChild(textarea);
184        }
185    });
186
187});

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.