PageSourceSearch

https://ognimohub.com/frontend/js/dashboard-mobile-app.js?v=2.3-1785423858

js ognimohub.com collected 2026-09-24 14:17:03 UTC 31,131 bytes, 831 lines download raw bytes

1/* =============================================================
2 | DigiKash — Mobile Dashboard JS controller
3 | Active only on viewports < 992px (matchMedia guard).
4 | Handles: sticky-header scroll state, scroll-aware tab bar,
5 | balance hide, sheets
6 | (More / Language / Apps / Notifications), wallet carousel
7 | dots, segmented insight switcher.
8 ============================================================= */
9(function () {
10    "use strict";
11
12    var mobileMq = window.matchMedia("(max-width: 991.98px)");
13
14    /* ---------- Body scroll lock ---------- */
15    var lockCount = 0;
16    var scrollBehaviorReady = false;
17    function lockBody() {
18        lockCount++;
19        document.body.classList.add("dk-scroll-lock");
20    }
21    function unlockBody() {
22        lockCount = Math.max(0, lockCount - 1);
23        if (lockCount === 0) document.body.classList.remove("dk-scroll-lock");
24    }
25
26    /* ---------- Sheet / panel registry ---------- */
27    var openables = [];
28    function registerOpenable(opener) {
29        openables.push(opener);
30    }
31    function closeAll() {
32        openables.forEach(function (o) { try { o.close(); } catch (e) {} });
33    }
34
35    /* ---------- Sheet helper (works for bottom sheet + side panel) ---------- */
36    function bindSheet(triggerSelector, panelSelector, backdropSelector, closeSelector) {
37        var panel = document.querySelector(panelSelector);
38        var backdrop = backdropSelector ? document.querySelector(backdropSelector) : null;
39        if (!panel) return null;
40        if (panel.__dkSheetApi) return panel.__dkSheetApi;
41
42        function triggers() {
43            return document.querySelectorAll(triggerSelector);
44        }
45
46        function open() {
47            // close any others first
48            openables.forEach(function (o) { if (o.panel !== panel) o.close(); });
49            panel.classList.add("is-open");
50            if (backdrop) backdrop.classList.add("is-open");
51            panel.setAttribute("aria-hidden", "false");
52            lockBody();
53            triggers().forEach(function (t) { t.setAttribute("aria-expanded", "true"); });
54        }
55        function close() {
56            if (!panel.classList.contains("is-open")) return;
57            panel.classList.remove("is-open");
58            if (backdrop) backdrop.classList.remove("is-open");
59            panel.setAttribute("aria-hidden", "true");
60            unlockBody();
61            triggers().forEach(function (t) { t.setAttribute("aria-expanded", "false"); });
62        }
63
64        document.addEventListener("click", function (e) {
65            var trigger = e.target.closest(triggerSelector);
66            if (!trigger) return;
67
68            e.preventDefault();
69            e.stopPropagation();
70
71            if (panel.classList.contains("is-open")) close();
72            else open();
73        });
74
75        if (backdrop) backdrop.addEventListener("click", close);
76        if (closeSelector) {
77            panel.querySelectorAll(closeSelector).forEach(function (b) {
78                b.addEventListener("click", close);
79            });
80        }
81        var api = { open: open, close: close, panel: panel };
82        panel.__dkSheetApi = api;
83        registerOpenable(api);
84        return api;
85    }
86
87    /* ---------- Sticky header scroll state + scroll-aware tab bar ---------- */
88    function initScrollBehavior() {
89        if (scrollBehaviorReady) return;
90        scrollBehaviorReady = true;
91
92        var header = document.querySelector(".dk-mobile-header");
93        var tabbar = document.querySelector(".dk-tabbar");
94
95        if (document.querySelector("[data-mobile-scroll-stable]")) {
96            if (header) header.setAttribute("data-scrolled", "0");
97            if (tabbar) tabbar.setAttribute("data-hidden", "0");
98
99            return;
100        }
101
102        var ticking = false;
103        var lastHeaderState = null;
104        var lastTabbarState = null;
105
106        function update() {
107            var y = window.scrollY || window.pageYOffset || 0;
108            var nextHeaderState = y > 8 ? "1" : "0";
109            var nextTabbarState = "0";
110
111            if (header && lastHeaderState !== nextHeaderState) {
112                header.setAttribute("data-scrolled", nextHeaderState);
113                lastHeaderState = nextHeaderState;
114            }
115
116            if (tabbar) {
117                if (lastTabbarState !== nextTabbarState) {
118                    tabbar.setAttribute("data-hidden", nextTabbarState);
119                    lastTabbarState = nextTabbarState;
120                }
121            }
122
123            ticking = false;
124        }
125        window.addEventListener("scroll", function () {
126            if (!ticking) {
127                window.requestAnimationFrame(update);
128                ticking = true;
129            }
130        }, { passive: true });
131        update();
132    }
133
134    /* ---------- Wallet carousel dots ---------- */
135    function initWalletDots() {
136        var track = document.querySelector(".dk-wallet-track");
137        var dots = document.querySelectorAll(".dk-dots span");
138        if (!track || !dots.length) return;
139        function setActive() {
140            var card = track.querySelector(".dk-wallet-card");
141            if (!card) return;
142            var step = card.offsetWidth + 12;
143            var idx = Math.round(track.scrollLeft / step);
144            idx = Math.max(0, Math.min(dots.length - 1, idx));
145            dots.forEach(function (d, i) { d.setAttribute("data-active", i === idx ? "1" : "0"); });
146        }
147        track.addEventListener("scroll", function () { window.requestAnimationFrame(setActive); }, { passive: true });
148        setActive();
149    }
150
151    /* ---------- Hide / show balance ---------- */
152    function initBalanceToggle() {
153        var stored = (function () { try { return localStorage.getItem("dk_hide_bal"); } catch (e) { return null; } })();
154        var hidden = stored === "1";
155        var balanceTargets = ".dk-wc__amount, [data-balance-mask]";
156        function apply() {
157            document.querySelectorAll(balanceTargets).forEach(function (el) {
158                el.setAttribute("data-hidden", hidden ? "1" : "0");
159            });
160            document.querySelectorAll(".dk-balance-eye i").forEach(function (i) {
161                i.className = hidden ? "fa fa-eye-slash" : "fa fa-eye";
162            });
163            document.querySelectorAll(".dk-balance-eye").forEach(function (btn) {
164                btn.setAttribute("aria-pressed", hidden ? "true" : "false");
165            });
166        }
167        function toggle(e) {
168            if (e) {
169                e.preventDefault();
170                e.stopPropagation();
171            }
172            hidden = !hidden;
173            try { localStorage.setItem("dk_hide_bal", hidden ? "1" : "0"); } catch (er) {}
174            apply();
175        }
176        document.querySelectorAll(".dk-balance-eye").forEach(function (btn) {
177            if (btn.__dkBalanceToggleBound) return;
178            btn.__dkBalanceToggleBound = true;
179            btn.addEventListener("click", toggle);
180            btn.addEventListener("keydown", function (e) {
181                if (e.key !== "Enter" && e.key !== " ") return;
182                toggle(e);
183            });
184        });
185        apply();
186    }
187
188    /* ---------- Insight segmented control ---------- */
189    function initInsightSegments() {
190        document.querySelectorAll(".dk-seg").forEach(function (seg) {
191            var btns = seg.querySelectorAll(".dk-seg__btn");
192            btns.forEach(function (b) {
193                b.addEventListener("click", function () {
194                    btns.forEach(function (x) {
195                        x.setAttribute("data-active", "0");
196                        x.setAttribute("aria-selected", "false");
197                    });
198                    b.setAttribute("data-active", "1");
199                    b.setAttribute("aria-selected", "true");
200                    var mode = b.getAttribute("data-mode");
201                    var insight = seg.closest(".dk-insight");
202                    if (!insight) return;
203                    insight.setAttribute("data-mode", mode);
204                    // Toggle big-number panels
205                    insight.querySelectorAll("[data-big]").forEach(function (p) {
206                        p.style.display = p.getAttribute("data-big") === mode ? "" : "none";
207                    });
208                    // Notify chart
209                    try {
210                        window.dispatchEvent(new CustomEvent("dk:insight-mode", { detail: { mode: mode } }));
211                    } catch (e) {}
212                });
213            });
214        });
215    }
216
217    function notify(type, message) {
218        if (window.notifyEvs && message) {
219            window.notifyEvs(type, message);
220        }
221    }
222
223    function copyTextToClipboard(text) {
224        if (navigator.clipboard && navigator.clipboard.writeText) {
225            return navigator.clipboard.writeText(text);
226        }
227
228        return new Promise(function (resolve, reject) {
229            var ta = document.createElement("textarea");
230            ta.value = text;
231            ta.setAttribute("readonly", "readonly");
232            ta.style.position = "fixed";
233            ta.style.top = "-1000px";
234            document.body.appendChild(ta);
235            ta.select();
236
237            try {
238                var ok = document.execCommand("copy");
239                document.body.removeChild(ta);
240                if (ok) resolve();
241                else reject(new Error("Copy command failed"));
242            } catch (error) {
243                document.body.removeChild(ta);
244                reject(error);
245            }
246        });
247    }
248
249    function markWalletQrButton(button) {
250        var label = button.querySelector("[data-wallet-qr-action-label]");
251        var successLabel = button.getAttribute("data-wallet-qr-success-label");
252
253        if (label && successLabel && !button.getAttribute("data-wallet-qr-default-label")) {
254            button.setAttribute("data-wallet-qr-default-label", label.textContent);
255        }
256
257        if (label && successLabel) {
258            label.textContent = successLabel;
259        }
260
261        button.classList.add("is-done");
262
263        setTimeout(function () {
264            if (label && successLabel) {
265                label.textContent = button.getAttribute("data-wallet-qr-default-label");
266            }
267            button.classList.remove("is-done");
268        }, 1200);
269    }
270
271    function walletQrSvg(button) {
272        var card = button.closest("[data-wallet-qr-card]");
273        if (!card) return null;
274
275        return card.querySelector("[data-wallet-qr-svg] svg");
276    }
277
278    function downloadWalletQr(button) {
279        var svg = walletQrSvg(button);
280        if (!svg) return;
281
282        var source = new XMLSerializer().serializeToString(svg);
283        if (source.indexOf("xmlns=") === -1) {
284            source = source.replace("<svg", '<svg xmlns="http://www.w3.org/2000/svg"');
285        }
286
287        var blob = new Blob([source], { type: "image/svg+xml;charset=utf-8" });
288        var url = window.URL.createObjectURL(blob);
289        var link = document.createElement("a");
290
291        link.href = url;
292        link.download = button.getAttribute("data-wallet-qr-filename") || "wallet-qr.svg";
293        document.body.appendChild(link);
294        link.click();
295        document.body.removeChild(link);
296        window.URL.revokeObjectURL(url);
297        markWalletQrButton(button);
298        notify("success", button.getAttribute("data-toast-message") || "QR code downloaded");
299    }
300
301    function initWalletQrActions() {
302        if (document.documentElement.getAttribute("data-wallet-qr-actions-ready") === "1") return;
303        document.documentElement.setAttribute("data-wallet-qr-actions-ready", "1");
304
305        document.addEventListener("click", function (event) {
306            var copyButton = event.target.closest("[data-wallet-qr-copy]");
307            var shareButton = event.target.closest("[data-wallet-qr-share]");
308            var downloadButton = event.target.closest("[data-wallet-qr-download]");
309
310            if (copyButton) {
311                event.preventDefault();
312                copyTextToClipboard(copyButton.getAttribute("data-wallet-qr-link") || "").then(function () {
313                    markWalletQrButton(copyButton);
314                    notify("success", copyButton.getAttribute("data-toast-message") || "Receive link copied");
315                }).catch(function () {
316                    notify("error", "Could not copy. Please copy the link manually.");
317                });
318                return;
319            }
320
321            if (shareButton) {
322                event.preventDefault();
323
324                var shareUrl = shareButton.getAttribute("data-wallet-qr-link") || "";
325                var shareData = {
326                    title: shareButton.getAttribute("data-wallet-qr-title") || document.title,
327                    text: shareButton.getAttribute("data-wallet-qr-text") || "",
328                    url: shareUrl,
329                };
330
331                if (navigator.share) {
332                    navigator.share(shareData).then(function () {
333                        markWalletQrButton(shareButton);
334                    }).catch(function () {});
335                    return;
336                }
337
338                copyTextToClipboard(shareUrl).then(function () {
339                    markWalletQrButton(shareButton);
340                    notify("success", shareButton.getAttribute("data-toast-message") || "Receive link copied");
341                }).catch(function () {
342                    notify("error", "Could not share. Please copy the link manually.");
343                });
344                return;
345            }
346
347            if (downloadButton) {
348                event.preventDefault();
349                downloadWalletQr(downloadButton);
350            }
351        });
352
353        document.querySelectorAll("[data-wallet-qr-link-input]").forEach(function (input) {
354            input.addEventListener("focus", function () { input.select(); });
355            input.addEventListener("click", function () { input.select(); });
356        });
357    }
358
359    /* ---------- Copy buttons ---------- */
360    function initCopy() {
361        document.querySelectorAll("[data-dk-copy]").forEach(function (btn) {
362            btn.addEventListener("click", function (e) {
363                e.preventDefault();
364                e.stopPropagation();
365                var text = btn.getAttribute("data-dk-copy");
366                if (!text) return;
367                copyTextToClipboard(text).then(function () {
368                    btn.classList.add("is-copied");
369                    setTimeout(function () { btn.classList.remove("is-copied"); }, 800);
370                }).catch(function () {});
371            });
372        });
373    }
374
375    /* ---------- KYC dismiss ---------- */
376    function initKycDismiss() {
377        document.querySelectorAll(".dk-kyc__close").forEach(function (b) {
378            b.addEventListener("click", function () {
379                var card = b.closest(".dk-kyc");
380                if (card) card.style.display = "none";
381            });
382        });
383    }
384
385    /* ---------- Esc closes panels ---------- */
386    function initEsc() {
387        document.addEventListener("keydown", function (e) {
388            if (e.key === "Escape") closeAll();
389        });
390    }
391
392    /* ---------- Payment QR scanner ---------- */
393    function initQrScanner() {
394        var modal = document.querySelector("[data-dk-qr-scanner-modal]");
395        var backdrop = document.querySelector("[data-dk-qr-scanner-backdrop]");
396        if (!modal || modal.getAttribute("data-ready") === "1") return;
397
398        modal.setAttribute("data-ready", "1");
399
400        var video = modal.querySelector("[data-dk-qr-scanner-video]");
401        var canvas = modal.querySelector("[data-dk-qr-scanner-canvas]");
402        var status = modal.querySelector("[data-dk-qr-scanner-status]");
403        var input = modal.querySelector("[data-dk-qr-scanner-input]");
404        var manualForm = modal.querySelector("[data-dk-qr-scanner-manual]");
405        var permissionButton = modal.querySelector("[data-dk-qr-scanner-permission]");
406        var closeButtons = modal.querySelectorAll("[data-dk-qr-scanner-close]");
407        var stream = null;
408        var detector = null;
409        var frameRequest = null;
410        var scanning = false;
411        var lastScanAt = 0;
412
413        function scannerButtons() {
414            return document.querySelectorAll("[data-dk-qr-scanner-open]");
415        }
416
417        function setScannerButtonsExpanded(isExpanded) {
418            scannerButtons().forEach(function (button) {
419                button.setAttribute("aria-expanded", isExpanded ? "true" : "false");
420            });
421        }
422
423        function message(key, fallback) {
424            return modal.getAttribute("data-" + key + "-label") || fallback;
425        }
426
427        function setStatus(text) {
428            if (status) status.textContent = text;
429        }
430
431        function showPermissionButton() {
432            if (permissionButton) permissionButton.hidden = false;
433        }
434
435        function hidePermissionButton() {
436            if (permissionButton) permissionButton.hidden = true;
437        }
438
439        function isPermissionError(error) {
440            return error && ["NotAllowedError", "PermissionDeniedError", "SecurityError"].indexOf(error.name) !== -1;
441        }
442
443        function escapeRegExp(value) {
444            return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
445        }
446
447        function configuredPaymentLinkBase() {
448            var fallback = window.location.origin + "/payment-link";
449            var rawBase = modal.getAttribute("data-payment-link-base-url") || fallback;
450
451            try {
452                return new URL(rawBase, window.location.origin);
453            } catch (error) {
454                return new URL(fallback);
455            }
456        }
457
458        function configuredSendMoneyBase() {
459            var fallback = window.location.origin + "/user/send-money/create";
460            var rawBase = modal.getAttribute("data-send-money-base-url") || fallback;
461
462            try {
463                return new URL(rawBase, window.location.origin);
464            } catch (error) {
465                return new URL(fallback);
466            }
467        }
468
469        function configuredAgentCashOutUrlTemplate() {
470            var fallback = window.location.origin + "/user/agent/qr/__TOKEN__/cash-out";
471            var rawTemplate = modal.getAttribute("data-agent-cash-out-url-template") || fallback;
472
473            try {
474                return new URL(rawTemplate, window.location.origin);
475            } catch (error) {
476                return new URL(fallback);
477            }
478        }
479
480        function looksLikeWalletId(value) {
481            return /^DK[-A-Z0-9]+$/i.test(value)
482                || /^[0-9]{6,}$/.test(value)
483                || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
484        }
485
486        function paymentLinkUrl(value) {
487            var raw = (value || "").trim();
488            if (!raw) return null;
489
490            if (/^[a-z0-9.-]+\.[a-z]{2,}(\/|$)/i.test(raw)) {
491                raw = "https://" + raw;
492            }
493
494            try {
495                var base = configuredPaymentLinkBase();
496                var pathPrefix = base.pathname.replace(/\/+$/, "") || "/payment-link";
497                var allowedOrigins = [window.location.origin];
498                if (allowedOrigins.indexOf(base.origin) === -1) allowedOrigins.push(base.origin);
499                var url = new URL(raw, window.location.origin);
500                var path = url.pathname.replace(/\/+$/, "");
501                var routePattern = new RegExp("^" + escapeRegExp(pathPrefix) + "/[A-Za-z0-9_-]+$");
502
503                if (allowedOrigins.indexOf(url.origin) === -1) return null;
504                if (!routePattern.test(path)) return null;
505
506                return url;
507            } catch (error) {
508                return null;
509            }
510        }
511
512        function sendMoneyUrl(value) {
513            var raw = (value || "").trim();
514            if (!raw) return null;
515
516            var base = configuredSendMoneyBase();
517
518            if (looksLikeWalletId(raw)) {
519                var walletUrl = new URL(base.href);
520                walletUrl.searchParams.set("recipient", raw);
521
522                return walletUrl;
523            }
524
525            if (/^[a-z0-9.-]+\.[a-z]{2,}(\/|$)/i.test(raw)) {
526                raw = "https://" + raw;
527            }
528
529            try {
530                var allowedOrigins = [window.location.origin];
531                if (allowedOrigins.indexOf(base.origin) === -1) allowedOrigins.push(base.origin);
532
533                var url = new URL(raw, window.location.origin);
534                var basePath = base.pathname.replace(/\/+$/, "");
535                var path = url.pathname.replace(/\/+$/, "");
536                var recipient = (url.searchParams.get("recipient") || "").trim();
537
538                if (allowedOrigins.indexOf(url.origin) === -1) return null;
539                if (path !== basePath) return null;
540                if (!recipient) return null;
541
542                return url;
543            } catch (error) {
544                return null;
545            }
546        }
547
548        function agentCashOutUrl(value) {
549            var raw = (value || "").trim();
550            if (!raw) return null;
551
552            var template = configuredAgentCashOutUrlTemplate();
553            var tokenPlaceholder = "__TOKEN__";
554
555            if (/^aqr_[A-Za-z0-9_-]+$/.test(raw)) {
556                var tokenUrl = new URL(template.href);
557                tokenUrl.pathname = tokenUrl.pathname.replace(tokenPlaceholder, raw);
558
559                return tokenUrl;
560            }
561
562            if (/^[a-z0-9.-]+\.[a-z]{2,}(\/|$)/i.test(raw)) {
563                raw = "https://" + raw;
564            }
565
566            try {
567                var allowedOrigins = [window.location.origin];
568                if (allowedOrigins.indexOf(template.origin) === -1) allowedOrigins.push(template.origin);
569
570                var url = new URL(raw, window.location.origin);
571                var templatePath = template.pathname.replace(/\/+$/, "");
572                var path = url.pathname.replace(/\/+$/, "");
573                var encodedPlaceholder = encodeURIComponent(tokenPlaceholder);
574                var routePattern = new RegExp(
575                    "^" + escapeRegExp(templatePath)
576                        .replace(escapeRegExp(tokenPlaceholder), "[A-Za-z0-9_-]+")
577                        .replace(escapeRegExp(encodedPlaceholder), "[A-Za-z0-9_-]+") + "$"
578                );
579
580                if (allowedOrigins.indexOf(url.origin) === -1) return null;
581                if (!routePattern.test(path)) return null;
582
583                return url;
584            } catch (error) {
585                return null;
586            }
587        }
588
589        function paymentQrUrl(value) {
590            return paymentLinkUrl(value) || sendMoneyUrl(value) || agentCashOutUrl(value);
591        }
592
593        function stopCamera() {
594            scanning = false;
595            if (frameRequest) {
596                window.cancelAnimationFrame(frameRequest);
597                frameRequest = null;
598            }
599            if (stream) {
600                stream.getTracks().forEach(function (track) { track.stop(); });
601                stream = null;
602            }
603            if (video) {
604                video.pause();
605                video.srcObject = null;
606            }
607        }
608
609        function closeScanner() {
610            if (!modal.classList.contains("is-open")) return;
611            stopCamera();
612            modal.classList.remove("is-open");
613            modal.setAttribute("aria-hidden", "true");
614            if (backdrop) backdrop.classList.remove("is-open");
615            setScannerButtonsExpanded(false);
616            unlockBody();
617        }
618
619        function openPayment(value) {
620            var url = paymentQrUrl(value);
621            if (!url) {
622                setStatus(message("invalid", "This QR code is not a valid payment, wallet, or agent QR."));
623                return false;
624            }
625
626            setStatus(message("detected", "QR code detected. Opening..."));
627            stopCamera();
628            window.location.href = url.href;
629
630            return true;
631        }
632
633        function scanFrame(now) {
634            if (!scanning || !detector || !video) return;
635
636            if (now - lastScanAt < 220 || video.readyState < 2) {
637                frameRequest = window.requestAnimationFrame(scanFrame);
638                return;
639            }
640
641            lastScanAt = now;
642            detector.detect(video).then(function (codes) {
643                if (codes && codes.length) {
644                    var value = codes[0].rawValue || "";
645                    if (openPayment(value)) return;
646                }
647
648                frameRequest = window.requestAnimationFrame(scanFrame);
649            }).catch(function () {
650                frameRequest = window.requestAnimationFrame(scanFrame);
651            });
652        }
653
654        function requestCamera() {
655            return navigator.mediaDevices.getUserMedia({
656                video: { facingMode: { ideal: "environment" } },
657                audio: false,
658            }).catch(function () {
659                return navigator.mediaDevices.getUserMedia({
660                    video: true,
661                    audio: false,
662                });
663            });
664        }
665
666        function createDetector() {
667            if (!("BarcodeDetector" in window)) {
668                return Promise.resolve(null);
669            }
670
671            return Promise.resolve(
672                window.BarcodeDetector.getSupportedFormats
673                    ? window.BarcodeDetector.getSupportedFormats()
674                    : ["qr_code"]
675            ).then(function (formats) {
676                if (formats.length && formats.indexOf("qr_code") === -1) {
677                    return null;
678                }
679
680                return new window.BarcodeDetector({ formats: ["qr_code"] });
681            });
682        }
683
684        function startCamera() {
685            stopCamera();
686            hidePermissionButton();
687            setStatus(message("starting", "Starting camera..."));
688
689            if (!video || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
690                setStatus(message("camera-error", "Camera access is blocked or unavailable. Paste the QR link below."));
691                showPermissionButton();
692                return;
693            }
694
695            requestCamera().then(function (mediaStream) {
696                if (!modal.classList.contains("is-open")) {
697                    mediaStream.getTracks().forEach(function (track) { track.stop(); });
698                    return Promise.reject(new Error("Scanner closed"));
699                }
700
701                stream = mediaStream;
702                video.srcObject = mediaStream;
703
704                return video.play();
705            }).then(function () {
706                return createDetector();
707            }).then(function (barcodeDetector) {
708                detector = barcodeDetector;
709
710                if (!detector) {
711                    if (canvas) canvas.hidden = true;
712                    setStatus(message("unsupported", "Camera QR scanning is not supported on this browser. Paste the QR link below."));
713                    return;
714                }
715
716                return null;
717            }).then(function () {
718                if (!modal.classList.contains("is-open")) {
719                    stopCamera();
720                    return;
721                }
722
723                if (!detector) {
724                    return;
725                }
726
727                scanning = true;
728                lastScanAt = 0;
729                hidePermissionButton();
730                setStatus(message("ready", "Point your camera at a payment link or wallet QR code."));
731                frameRequest = window.requestAnimationFrame(scanFrame);
732            }).catch(function (error) {
733                if (error && error.message === "Scanner closed") return;
734                showPermissionButton();
735                setStatus(
736                    isPermissionError(error)
737                        ? message("camera-denied", "Camera permission is blocked. Enable camera access from browser site settings, then tap Allow Camera.")
738                        : message("camera-permission", "Camera permission is required to scan QR codes. Tap Allow Camera and approve access.")
739                );
740            });
741        }
742
743        function openScanner() {
744            if (modal.classList.contains("is-open")) return;
745            closeAll();
746            modal.classList.add("is-open");
747            modal.setAttribute("aria-hidden", "false");
748            if (backdrop) backdrop.classList.add("is-open");
749            setScannerButtonsExpanded(true);
750            lockBody();
751            startCamera();
752        }
753
754        if (permissionButton) {
755            permissionButton.addEventListener("click", function (event) {
756                event.preventDefault();
757                startCamera();
758            });
759        }
760
761        document.addEventListener("click", function (event) {
762            var button = event.target.closest("[data-dk-qr-scanner-open]");
763            if (!button) return;
764
765            event.preventDefault();
766            openScanner();
767        });
768
769        closeButtons.forEach(function (button) {
770            button.addEventListener("click", closeScanner);
771        });
772
773        if (backdrop) {
774            backdrop.addEventListener("click", closeScanner);
775        }
776
777        if (manualForm) {
778            manualForm.addEventListener("submit", function (event) {
779                event.preventDefault();
780                openPayment(input ? input.value : "");
781            });
782        }
783
784        document.addEventListener("keydown", function (event) {
785            if (event.key === "Escape") closeScanner();
786        });
787    }
788
789    /* ---------- Init only on mobile widths ---------- */
790    function bootMobile() {
791        if (!mobileMq.matches) return;
792        bindSheet(".dk-open-more",    ".dk-more-sheet",    ".dk-more-backdrop",    ".dk-sheet-close");
793        bindSheet(".dk-open-lang",    ".dk-lang-sheet",    ".dk-lang-backdrop",    ".dk-sheet-close");
794        bindSheet(".dk-open-apps",    ".dk-apps-sheet",    ".dk-apps-backdrop",    ".dk-sheet-close");
795        bindSheet(".dk-open-notif",   ".dk-notif-panel",   ".dk-notif-backdrop",   ".dk-sheet-close");
796        initScrollBehavior();
797        initWalletDots();
798        initInsightSegments();
799        initCopy();
800        initKycDismiss();
801        initEsc();
802    }
803
804    function bootShared() {
805        initBalanceToggle();
806        initQrScanner();
807        initWalletQrActions();
808    }
809
810    if (document.readyState === "loading") {
811        document.addEventListener("DOMContentLoaded", function () {
812            bootMobile();
813            bootShared();
814        });
815    } else {
816        bootMobile();
817        bootShared();
818    }
819
820    // Also run on viewport change so resizing into mobile activates.
821    if (mobileMq.addEventListener) {
822        mobileMq.addEventListener("change", function () { bootMobile(); });
823    } else if (mobileMq.addListener) {
824        mobileMq.addListener(function () { bootMobile(); });
825    }
826
827    // Expose for inline use.
828    window.DKMobile = {
829        closeAll: closeAll,
830    };
831})();

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.