PageSourceSearch

https://neowavechart.com/static/js/popup.74a102acc992.js

js neowavechart.com collected 2026-10-05 23:19:36 UTC 8,620 bytes, 223 lines download raw bytes

1(function () {
2    "use strict";
3
4    document.addEventListener("DOMContentLoaded", function () {
5        const dialog = document.getElementById("popupOverlay");
6        if (!dialog) return;
7
8        const popupId = dialog.dataset.popupId || "unknown";
9        const popupVersion = dialog.dataset.popupVersion || "0";
10        const frequency = dialog.dataset.popupFrequency || "once";
11        const presentation = dialog.dataset.popupPresentation || "modal";
12        const repeatDays = Number(dialog.dataset.popupRepeatDays || 7);
13        const trigger = dialog.dataset.popupTrigger || "delay";
14        const delayMs = Math.max(0, Number(dialog.dataset.popupDelay || 0) * 1000);
15        const scrollDepth = Math.min(100, Math.max(10, Number(dialog.dataset.popupScroll || 50)));
16        const storageKey = `neowave_popup:${popupId}:${popupVersion}`;
17        const legacyStorageKey = `popupDismissed_${popupId}_${popupVersion}`;
18        const closeButton = dialog.querySelector("[data-popup-close]");
19        const popupImage = dialog.querySelector(".popup-image");
20        const analyticsForm = dialog.querySelector("[data-popup-analytics]");
21        const csrfToken = analyticsForm?.querySelector("[name=csrfmiddlewaretoken]")?.value || "";
22        const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
23        let showTimer = null;
24        let closingTimer = null;
25        let isClosing = false;
26        let dismissalTracked = false;
27
28        function hideBrokenImage() {
29            const media = popupImage?.closest(".popup-media");
30            if (media) media.hidden = true;
31        }
32
33        popupImage?.addEventListener("error", hideBrokenImage);
34        if (popupImage?.complete && popupImage.naturalWidth === 0) hideBrokenImage();
35
36        function storageGet(storage, key) {
37            try {
38                return storage.getItem(key);
39            } catch (error) {
40                return null;
41            }
42        }
43
44        function storageSet(storage, key, value) {
45            try {
46                storage.setItem(key, value);
47                return true;
48            } catch (error) {
49                dialog.dataset.dismissed = value;
50                return false;
51            }
52        }
53
54        function isDismissed() {
55            if (dialog.dataset.dismissed) return true;
56            if (frequency === "always") return false;
57            if (frequency === "session") {
58                return Boolean(storageGet(window.sessionStorage, storageKey));
59            }
60
61            const savedValue = storageGet(window.localStorage, storageKey);
62            if (!savedValue && frequency === "once") {
63                return Boolean(storageGet(window.localStorage, legacyStorageKey));
64            }
65            if (!savedValue) return false;
66            if (frequency === "days") {
67                const dismissedAt = Number(savedValue);
68                const duration = repeatDays * 24 * 60 * 60 * 1000;
69                return Number.isFinite(dismissedAt) && Date.now() - dismissedAt < duration;
70            }
71            return true;
72        }
73
74        function markDismissed() {
75            const value = frequency === "days" ? String(Date.now()) : "1";
76            if (frequency === "always") {
77                dialog.dataset.dismissed = value;
78            } else if (frequency === "session") {
79                storageSet(window.sessionStorage, storageKey, value);
80            } else {
81                storageSet(window.localStorage, storageKey, value);
82            }
83        }
84
85        function sendEvent(eventName) {
86            if (!analyticsForm || !csrfToken) return;
87            const body = new URLSearchParams({event: eventName});
88            window.fetch(analyticsForm.action, {
89                method: "POST",
90                body,
91                credentials: "same-origin",
92                keepalive: true,
93                headers: {
94                    "X-CSRFToken": csrfToken,
95                    "X-Requested-With": "XMLHttpRequest",
96                },
97            }).catch(function () {
98                // Campaign metrics are best-effort and never block the visitor.
99            });
100        }
101
102        function clearTriggers() {
103            if (showTimer) {
104                window.clearTimeout(showTimer);
105                showTimer = null;
106            }
107            window.removeEventListener("scroll", checkScroll);
108            document.removeEventListener("mouseout", checkExitIntent);
109        }
110
111        function showPopup() {
112            if (isDismissed() || dialog.open) return;
113            clearTriggers();
114
115            if (presentation === "card" && typeof dialog.show === "function") {
116                dialog.show();
117            } else if (typeof dialog.showModal === "function") {
118                dialog.showModal();
119            } else {
120                dialog.setAttribute("open", "");
121                if (presentation !== "card") dialog.setAttribute("aria-modal", "true");
122            }
123
124            if (presentation !== "card") document.body.classList.add("popup-open");
125            window.requestAnimationFrame(function () {
126                dialog.classList.add("is-open");
127                if (presentation !== "card") closeButton?.focus({preventScroll: true});
128            });
129            sendEvent("impression");
130        }
131
132        function finishClose() {
133            if (dialog.open && typeof dialog.close === "function") {
134                dialog.close();
135            } else {
136                dialog.removeAttribute("open");
137                document.body.classList.remove("popup-open");
138            }
139        }
140
141        function dismissPopup() {
142            if (!dialog.open || isClosing) return;
143            isClosing = true;
144            markDismissed();
145            if (!dismissalTracked) {
146                sendEvent("dismiss");
147                dismissalTracked = true;
148            }
149            dialog.classList.remove("is-open");
150            dialog.classList.add("is-closing");
151            closingTimer = window.setTimeout(finishClose, reduceMotion ? 0 : 180);
152        }
153
154        function checkScroll() {
155            const documentHeight = Math.max(
156                document.documentElement.scrollHeight,
157                document.body.scrollHeight
158            );
159            const scrollable = documentHeight - window.innerHeight;
160            if (scrollable <= 0) {
161                clearTriggers();
162                showTimer = window.setTimeout(showPopup, delayMs);
163                return;
164            }
165            const progress = (window.scrollY / scrollable) * 100;
166            if (progress >= scrollDepth) showPopup();
167        }
168
169        function checkExitIntent(event) {
170            if (!event.relatedTarget && event.clientY <= 8) showPopup();
171        }
172
173        closeButton?.addEventListener("click", dismissPopup);
174
175        dialog.addEventListener("cancel", function (event) {
176            event.preventDefault();
177            dismissPopup();
178        });
179
180        document.addEventListener("keydown", function (event) {
181            if (presentation === "card" && event.key === "Escape" && dialog.open) {
182                dismissPopup();
183            }
184        });
185
186        dialog.addEventListener("click", function (event) {
187            if (event.target !== dialog) return;
188            const rect = dialog.getBoundingClientRect();
189            const inside = (
190                event.clientX >= rect.left
191                && event.clientX <= rect.right
192                && event.clientY >= rect.top
193                && event.clientY <= rect.bottom
194            );
195            if (!inside) dismissPopup();
196        });
197
198        dialog.addEventListener("close", function () {
199            if (closingTimer) window.clearTimeout(closingTimer);
200            closingTimer = null;
201            isClosing = false;
202            dialog.classList.remove("is-open", "is-closing");
203            document.body.classList.remove("popup-open");
204        });
205
206        dialog.querySelectorAll("[data-popup-action]").forEach(function (action) {
207            action.addEventListener("click", function () {
208                markDismissed();
209                sendEvent(action.dataset.popupAction);
210            });
211        });
212
213        if (isDismissed()) return;
214        if (trigger === "scroll") {
215            window.addEventListener("scroll", checkScroll, {passive: true});
216            checkScroll();
217        } else if (trigger === "exit" && window.matchMedia("(pointer: fine)").matches) {
218            document.addEventListener("mouseout", checkExitIntent);
219        } else {
220            showTimer = window.setTimeout(showPopup, delayMs);
221        }
222    });
223})();

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.