PageSourceSearch

https://darumacasino.us/assets/wild/main-4ea5cdd7d3428964f0fc6056f0e1900cb68f276bd4b84c12d4a360b0aaa657d6.js

js darumacasino.us collected 2026-10-09 04:50:56 UTC 61,308 bytes, 1,512 lines download raw bytes

1import { messageFromGameFrame } from 'wild/game_frame';
2
3// Drag handler functions
4function closeMenu(menuType) {
5    const config = {
6        mobile: {
7            menuSelector: '.navbar',
8            activeClass: 'active-mobile',
9            buttonSelector: '#mobile-menu-link',
10            removeOverflow: true
11        },
12        social: {
13            menuSelector: '.social-menu-navbar',
14            activeClass: 'active',
15            buttonSelector: '#social-menu-modal-link',
16            removeOverflow: false,
17            hasChat: true
18        }
19    };
20
21    const cfg = config[menuType];
22    if (!cfg) return;
23
24    const menu = document.querySelector(cfg.menuSelector);
25    if (!menu) return;
26
27    // Get current transform value for smooth animation from current position
28    const currentTransform = menu.style.transform;
29    const currentY = currentTransform ? parseInt(currentTransform.match(/translateY\((-?\d+)px\)/)?.[1]) || 0 : 0;
30
31    // Enable transition for smooth animation
32    menu.style.transition = 'transform 0.3s ease-in-out';
33
34    // Animate to fully closed position from current position
35    menu.style.transform = `translateY(${menu.offsetHeight + Math.max(currentY, 0)}px)`;
36
37    setTimeout(() => {
38        // Remove active class and button state
39        menu.classList.remove(cfg.activeClass);
40        document.querySelector(cfg.buttonSelector)?.classList.remove('active');
41
42        // Handle overflow for mobile menu
43        if (cfg.removeOverflow) {
44            document.body.classList.remove('overflow-hidden');
45        }
46
47        // Handle chat for social menu
48        if (cfg.hasChat) {
49            const chatContainer = menu.querySelector('.social-chat-container');
50            if (chatContainer && chatContainer.classList.contains('active')) {
51                chatContainer.classList.remove('active');
52                menu.classList.remove('chat-active');
53            }
54        }
55
56        // Clear manual styles
57        menu.style.transform = '';
58        menu.style.transition = '';
59    }, 300);
60}
61
62function setupDragHandler(menuElement, pullerElement, closeCallback, preventDragCondition = null) {
63    if (!menuElement || !pullerElement) return;
64
65    let startY, isDragging = false;
66
67    const handleTouchStart = (e) => {
68        // Check prevent condition (e.g., chat-active for social menu)
69        if (preventDragCondition && preventDragCondition()) return;
70
71        startY = e.touches[0].clientY;
72        isDragging = true;
73        // Disable transition during drag
74        menuElement.style.transition = 'none';
75    };
76
77    const handleTouchMove = (e) => {
78        if (!isDragging) return;
79
80        const currentY = e.touches[0].clientY;
81        const deltaY = currentY - startY;
82
83        if (deltaY > 0) {
84            menuElement.style.transform = `translateY(${deltaY}px)`;
85        }
86    };
87
88    const handleTouchEnd = (e) => {
89        if (!isDragging) return;
90
91        isDragging = false;
92        const deltaY = e.changedTouches[0].clientY - startY;
93
94        // Re-enable transition for smooth animation
95        menuElement.style.transition = 'transform 0.3s ease-in-out';
96
97        if (deltaY > 100) {
98            // Use provided close callback
99            if (typeof closeCallback === 'function') {
100                closeCallback();
101            }
102        } else {
103            // Snap back to original position
104            menuElement.style.transform = 'translateY(0)';
105            setTimeout(() => {
106                menuElement.style.transition = '';
107            }, 300);
108        }
109    };
110
111    pullerElement.addEventListener('touchstart', handleTouchStart, { passive: true });
112    pullerElement.addEventListener('touchmove', handleTouchMove, { passive: true });
113    pullerElement.addEventListener('touchend', handleTouchEnd);
114}
115
116// Games run cross-origin and post `{ type: 'open-cashier' }` to reach the cashier. The source
117// check matters: any embed on the page (chat, KYC, tag manager) can post a message too.
118window.addEventListener('message', (event) => {
119    const data = event.data;
120    const type = typeof data === 'string' ? data : data?.type || data?.action;
121
122    if (type !== 'open-cashier' || !messageFromGameFrame(event.source)) return;
123
124    const detail = {};
125    const amount = data?.amount;
126    const packageId = data?.packageId ?? data?.package_id;
127
128    if (amount !== undefined) detail.amount = amount;
129    if (packageId !== undefined) detail.packageId = packageId;
130    // `pair` is threaded through for the Fluid cashier only; Coinflow ignores it (Phase-1 USD).
131    if (data?.pair !== undefined) detail.pair = data.pair;
132
133    // Coinflow-first, mirroring the click bridge below: prefer the Coinflow cashier when mounted,
134    // fall back to Fluid, otherwise the player has no cashier mounted.
135    if (document.querySelector('[data-controller="coinflow-cashier"]')) {
136        window.dispatchEvent(new CustomEvent('open-coinflow-cashier', { detail }));
137        return;
138    }
139
140    if (document.getElementById('fluid-widget-global')) {
141        window.dispatchEvent(new CustomEvent('open-fluid-cashier', { detail }));
142        return;
143    }
144
145    console.warn('open-cashier ignored: cashier is not mounted for this player');
146});
147
148document.addEventListener("turbo:load", function (event) {
149    let csrfToken = document.querySelector("meta[name='csrf-token']").content
150    document.querySelectorAll("form input[name='authenticity_token']").forEach(function (el) {
151      el.value = csrfToken
152    })
153  });
154
155document.addEventListener("turbo:morph", function (event) {
156    let csrfToken = document.querySelector("meta[name='csrf-token']").content
157    document.querySelectorAll("form input[name='authenticity_token']").forEach(function (el) {
158        el.value = csrfToken
159    })
160});
161
162// XtremePush injects popups and notification banners at <body> level; a Turbo snapshot
163// of them comes back as inert ghost copies on restore visits.
164document.addEventListener("turbo:before-cache", function () {
165    document
166        .querySelectorAll(".webpush-swal2-container, #webpush-local-notification-stack")
167        .forEach(function (el) { el.remove() });
168    [document.documentElement, document.body].forEach(function (el) {
169        el.classList.remove("webpush-swal2-shown", "webpush-swal2-iosfix", "webpush-swal2-toast-shown")
170    });
171});
172
173$(document).on("turbo:before-fetch-response", (event) => {
174    const fetchResponse = event.detail.fetchResponse;
175    let redirectUrl = fetchResponse.header("Turbo-Visit");
176    if (!redirectUrl) {
177      if (fetchResponse.redirected === true) {
178        redirectUrl = fetchResponse.location.href;
179      }
180    }
181
182    if (redirectUrl) {
183      // If user was logged in, verify session is still active before following redirect
184      const wasLoggedIn = !!document.querySelector('meta[name="player-id"]')
185      if (wasLoggedIn) {
186        event.preventDefault()
187        fetchResponse.response.clone().text().then(html => {
188          if (!html.includes('name="player-id"')) {
189            if (window.Turbo) Turbo.cache.clear()
190            window.location.reload()
191          } else {
192            if (typeof $.fancybox !== 'undefined') $.fancybox.close()
193            window.location.href = redirectUrl
194          }
195        })
196        return
197      }
198
199      if (typeof $.fancybox !== 'undefined') {
200        $.fancybox.close();
201      }
202
203      window.location.href = redirectUrl;
204    }
205});
206
207$(document).ready(function () {
208});
209
210window.listenersInitialized = false
211
212// The rakeback pop-up's URL anchor is brand-specific: Vegas Builder and SlotHub ship the feature
213// as "Rakeback" and must not surface Coinz's "coinzback" in the address bar; other wild brands keep it.
214const RAKEBACK_BRANDS = ['vegasbuilder', 'farmspins', 'slothub'];
215const RAKEBACK_HASH = RAKEBACK_BRANDS.includes(document.body?.dataset?.subBrandName) ? 'rakeback' : 'coinzback';
216
217const MODAL_HASH_MAP = {
218    'dailyquest': 'dailyChallengeModal',
219    'races': 'leaderboardModal',
220    'refer': 'referralStatsModal',
221    'bonus_code': 'bonusCodeModal',
222    [RAKEBACK_HASH]: 'rakebackModal'
223};
224
225// Game iframes can post arbitrary messages — trust only our own origin and *.shuftipro.com.
226function trustedKycMessageOrigin(origin) {
227    if (origin === window.location.origin) return true;
228
229    try {
230        const host = new URL(origin).hostname;
231        return host === 'shuftipro.com' || host.endsWith('.shuftipro.com');
232    } catch {
233        return false;
234    }
235}
236
237// The reloaded verification tab can re-post the same terminal event while the webhook lags — act on each once.
238function firstKycStatusHandling(status) {
239    const key = `kycStatusHandledAt:${status}`;
240    const handledAt = parseInt(sessionStorage.getItem(key), 10);
241    if (!isNaN(handledAt) && Date.now() - handledAt < 90000) return false;
242
243    sessionStorage.setItem(key, String(Date.now()));
244    return true;
245}
246
247// Every Swiper built by initializeSliders, so the ones Turbo detaches can be torn down.
248window.wildSwiperRegistry = window.wildSwiperRegistry || [];
249
250function trackSwiper(swiper) {
251    window.wildSwiperRegistry.push(swiper);
252    return swiper;
253}
254
255function releaseDetachedSwipers() {
256    window.wildSwiperRegistry = window.wildSwiperRegistry.filter(function (swiper) {
257        if (swiper.el && swiper.el.isConnected) return true;
258
259        try {
260            swiper.destroy(true, false);
261        } catch (e) {
262            // already torn down — nothing to release
263        }
264        return false;
265    });
266}
267
268$(document).on("ready turbo:load", function() {
269    function initializeSliders() {
270        // Turbo replaces #games (and #lobby-carousels) wholesale on every client-side hop, so the
271        // element a Swiper was built on gets detached and a fresh one takes its place. Swiper's
272        // observeParents puts a MutationObserver on <body>, which keeps the old instance — and its
273        // autoplay rAF loop — alive forever. Nothing else destroys them, so do it here.
274        releaseDetachedSwipers();
275
276        // Touch devices scroll the horizontal rows natively (Swiper cssMode) instead of via JS
277        // transforms — see the per-slider configs below. Desktop keeps transform mode.
278        const useNativeScroll = window.matchMedia('(pointer: coarse)').matches;
279        // CORE-454 (behind lobby_dynamic_refresh via the body.lobby-refresh class from the layout):
280        // whole-tile rails (6 desktop / 4 mobile), one-view chevron paging, mobile chevrons in sync.
281        const lobbyRefresh = document.body.classList.contains('lobby-refresh');
282
283        $(".slots__slider").each(function (index) {
284            const $sliderContainer = $(this);
285            const $sliderParent = $sliderContainer.closest(".slots__items");
286
287            // "Game suggestions" (search modal) is a native horizontal scroller, not a
288            // Swiper — it lives in a display:none fancybox modal, so Swiper here measured a
289            // 0-width container and every arrow/swipe moved a pixel. See games_search.js.
290            if (this.id === 'slider-search-recommended') return;
291
292            // Check if Swiper is already initialized on this element
293            if (this.swiper) {
294                console.warn('Swiper already initialized on slots slider, skipping')
295                return
296            }
297
298            // "Today's Matches" carousel needs wide composite VS-pair slides;
299            // let the slide's CSS width win instead of dividing the container into 5.5.
300            const isVsPair = $sliderContainer.closest('.vs-pair-section').length > 0;
301
302            // Touch: scroll every game row natively instead of via Swiper cssMode, which is
303            // flaky on iOS Safari (arrows/swipe barely move — a pixel — on every row). Slide
304            // widths come from the .slots__slider:not(.swiper-initialized) pre-init CSS, so we
305            // just flip overflow to scroll and drive the edge-blur + arrows ourselves. Desktop
306            // keeps Swiper below (JS transform) for the hover lift. VS-pair rows keep Swiper —
307            // their fixed CSS slide width would be overridden by the pre-init rule in native mode.
308            if (useNativeScroll && !isVsPair) {
309                if (this._nativeSlider) return;
310                this._nativeSlider = true;
311
312                const scroller = this;
313                scroller.style.overflowX = 'auto';
314                scroller.style.overflowY = 'hidden';
315                // Allow BOTH axes: horizontal pans the carousel, vertical falls through to
316                // the page (the row has overflow-y:hidden). Bare pan-x blocks page scroll;
317                // auto lets iOS's page scroll steal the horizontal gesture.
318                scroller.style.touchAction = 'pan-x pan-y';
319                scroller.style.webkitOverflowScrolling = 'touch';
320                scroller.style.scrollBehavior = 'smooth';
321
322                if (lobbyRefresh) {
323                    // Mirror the provider carousel: dim + disable a chevron at the scroll extreme
324                    // (kept in sync with swipe), and advance one full view (4 tiles == clientWidth).
325                    const prevBtn = $sliderParent.find('.swiper-button-prev')[0];
326                    const nextBtn = $sliderParent.find('.swiper-button-next')[0];
327                    const updateNativeNav = function () {
328                        const atStart = scroller.scrollLeft <= 1;
329                        const atEnd = scroller.scrollLeft + scroller.clientWidth >= scroller.scrollWidth - 1;
330                        if (prevBtn) prevBtn.toggleAttribute('disabled', atStart);
331                        if (nextBtn) nextBtn.toggleAttribute('disabled', atEnd);
332                    };
333                    scroller.addEventListener('scroll', updateNativeNav, { passive: true });
334                    // Recompute after layout/images settle — a single rAF can run before the rail has
335                    // its final scrollWidth, which would otherwise leave a chevron dimmed at start.
336                    requestAnimationFrame(updateNativeNav);
337                    setTimeout(updateNativeNav, 300);
338                    const bindRefreshArrow = function (btn, direction) {
339                        if (!btn) return;
340                        btn.addEventListener('click', function (e) {
341                            e.preventDefault();
342                            scroller.scrollBy({ left: direction * scroller.clientWidth, behavior: 'smooth' });
343                        });
344                    };
345                    bindRefreshArrow(nextBtn, 1);
346                    bindRefreshArrow(prevBtn, -1);
347                    return;
348                }
349
350                const updateNativeBlur = function () {
351                    scroller.classList.toggle('is-blur-left', scroller.scrollLeft > 2);
352                    scroller.classList.toggle(
353                        'is-blur-no-right',
354                        scroller.scrollLeft + scroller.clientWidth >= scroller.scrollWidth - 2
355                    );
356                };
357                scroller.addEventListener('scroll', updateNativeBlur, { passive: true });
358                updateNativeBlur();
359
360                const stepBy = function () {
361                    const slide = scroller.querySelector('.swiper-slide');
362                    return slide ? slide.getBoundingClientRect().width + 12 : Math.round(scroller.clientWidth * 0.8);
363                };
364                const bindArrow = function (btn, direction) {
365                    if (!btn) return;
366                    btn.addEventListener('click', function (e) {
367                        e.preventDefault();
368                        scroller.scrollBy({ left: direction * stepBy(), behavior: 'smooth' });
369                    });
370                };
371                bindArrow($sliderParent.find('.swiper-button-next')[0], 1);
372                bindArrow($sliderParent.find('.swiper-button-prev')[0], -1);
373                return;
374            }
375
376            // Edge blur follows scroll position: left fade off at the start, right off at the end.
377            const updateBlur = function (swiper) {
378                const el = swiper.el;
379                el.classList.toggle("is-blur-left", !swiper.isBeginning);
380                el.classList.toggle("is-blur-no-right", swiper.isEnd);
381            };
382
383            const slider = trackSwiper(new Swiper(this, {
384                // On touch devices scroll natively instead of via JS transforms: iOS Safari won't
385                // let a bounded transform slider swipe back once it hits the end. Native overflow
386                // scroll keeps a real start/end yet scrolls both ways (fixes the stuck-at-end bug).
387                cssMode: useNativeScroll,
388                observer: true,
389                observeParents: true,
390                spaceBetween: isVsPair ? 16 : 12,
391                // CORE-454 (lobbyRefresh): whole tiles matching the All grid (6 desktop / 4 mobile),
392                // chevron advances one full view (slidesPerGroup == view). Off: the original 5.5/4.1.
393                slidesPerView: isVsPair ? 'auto' : (lobbyRefresh ? 6 : 5.5),
394                slidesPerGroup: isVsPair ? 1 : (lobbyRefresh ? 6 : 1),
395                navigation: {
396                    nextEl: $sliderParent.find(".swiper-button-next")[0],
397                    prevEl: $sliderParent.find(".swiper-button-prev")[0],
398                },
399                wrapperClass: "swiper-wrapper",
400                slideClass: "swiper-slide",
401                breakpoints: isVsPair ? {} : (lobbyRefresh ? {
402                    320: {
403                        slidesPerView: 4,
404                        slidesPerGroup: 4,
405                        spaceBetween: 8,
406                    },
407                    768: {
408                        slidesPerView: 6,
409                        slidesPerGroup: 6,
410                    },
411                } : {
412                    320: {
413                        slidesPerView: 4.1,
414                        spaceBetween: 8,
415                    },
416                    767: {
417                        slidesPerView: 5.5,
418                    },
419                    991: {
420                        slidesPerView: 5.5,
421                    },
422                    1260: {
423                        slidesPerView: 5.5,
424                    },
425                }),
426                on: {
427                    init: updateBlur,
428                    setTranslate: updateBlur,
429                    resize: updateBlur,
430                    observerUpdate: updateBlur,
431                },
432            }));
433
434            $sliderContainer.data("swiper", slider);
435        });
436
437        // Recent Wins uses a pure-CSS marquee instead of Swiper.
438        // Swiper's freeMode + loop + autoplay doesn't pause/resume
439        // cleanly on hover; a CSS keyframe animation with
440        // animation-play-state gives us reliable pause-on-hover for
441        // free. Cards are cloned once so the -50% loop wraps seamlessly.
442        // Available Packs row — Swiper with shared swiper__nav buttons,
443        // so it matches the look of every other carousel on the page.
444        $(".lobby-coins__scroll.swiper").each(function () {
445            if (this.swiper) {
446                console.warn('Swiper already initialized on lobby-coins, skipping');
447                return;
448            }
449            const $section = $(this).closest(".lobby-coins");
450
451            // On touch, scroll the packs strip natively instead of via Swiper. The
452            // cards have fixed CSS widths and the inline flex guard already lays them
453            // out, so Swiper only added a flaky cssMode snap grid that made swipe/arrows
454            // barely move. Pure overflow-x scroll needs no measurement and is robust.
455            // Desktop keeps Swiper below (hover lift relies on its overflow-visible layout).
456            if (useNativeScroll) {
457                if (this._nativePacks) return;
458                this._nativePacks = true;
459
460                const scroller = this;
461                scroller.style.overflow = '';
462                scroller.style.overflowX = 'auto';
463                scroller.style.overflowY = 'hidden';
464                // Allow BOTH axes: horizontal pans the carousel, vertical falls through to
465                // the page (the row has overflow-y:hidden). Bare pan-x blocks page scroll;
466                // auto lets iOS's page scroll steal the horizontal gesture.
467                scroller.style.touchAction = 'pan-x pan-y';
468                scroller.style.webkitOverflowScrolling = 'touch';
469                scroller.style.scrollBehavior = 'smooth';
470
471                const updatePacksBlur = function () {
472                    scroller.classList.toggle('is-blur-left', scroller.scrollLeft > 2);
473                    scroller.classList.toggle(
474                        'is-blur-no-right',
475                        scroller.scrollLeft + scroller.clientWidth >= scroller.scrollWidth - 2
476                    );
477                };
478                scroller.addEventListener('scroll', updatePacksBlur, { passive: true });
479                updatePacksBlur();
480
481                const stepBy = function () {
482                    const slide = scroller.querySelector('.swiper-slide');
483                    return slide ? slide.getBoundingClientRect().width + 12 : Math.round(scroller.clientWidth * 0.8);
484                };
485                const bindArrow = function (btn, direction) {
486                    if (!btn) return;
487                    btn.addEventListener('click', function (e) {
488                        e.preventDefault();
489                        scroller.scrollBy({ left: direction * stepBy(), behavior: 'smooth' });
490                    });
491                };
492                bindArrow($section.find('.swiper-button-next')[0], 1);
493                bindArrow($section.find('.swiper-button-prev')[0], -1);
494                return;
495            }
496
497            const wrapper = this.querySelector('.swiper-wrapper');
498            // Clear the pre-render guard styles before Swiper takes
499            // over so its inline transform / sizing isn't fighting our
500            // fallback flex layout.
501            this.style.overflow = '';
502            if (wrapper) {
503                wrapper.style.display = '';
504                wrapper.style.flexWrap = '';
505                wrapper.style.gap = '';
506            }
507            // Edge blur follows scroll position: left fade off at the start, right off at the end.
508            const updatePacksBlur = function (swiper) {
509                const el = swiper.el;
510                el.classList.toggle("is-blur-left", !swiper.isBeginning);
511                el.classList.toggle("is-blur-no-right", swiper.isEnd);
512            };
513            trackSwiper(new Swiper(this, {
514                // Native scroll on touch so the packs strip swipes back from the end on iOS
515                // mobile while still having a real start/end (see the game-row config above).
516                cssMode: useNativeScroll,
517                observer: true,
518                observeParents: true,
519                slidesPerView: "auto",
520                spaceBetween: 12,
521                navigation: {
522                    nextEl: $section.find(".swiper-button-next")[0],
523                    prevEl: $section.find(".swiper-button-prev")[0],
524                },
525                wrapperClass: "swiper-wrapper",
526                slideClass: "swiper-slide",
527                on: {
528                    init: updatePacksBlur,
529                    setTranslate: updatePacksBlur,
530                    resize: updatePacksBlur,
531                    observerUpdate: updatePacksBlur,
532                },
533            }));
534        });
535
536        // Recent Wins — full Swiper with autoplay, nav arrows, and
537        // touch swipe so it scrolls like every other carousel on the
538        // page. Autoplay drives the marquee feel; pauseOnMouseEnter
539        // halts it while the cursor is over the strip and touch / nav
540        // taps take over on mobile.
541        const winnerCardsElement = document.querySelector(".winner__cards");
542        if (winnerCardsElement && !winnerCardsElement.swiper) {
543            const wrapper = winnerCardsElement.querySelector('.swiper-wrapper');
544            // Clear the pre-render guard styles before Swiper takes
545            // over so it can manage transforms / sizing cleanly.
546            winnerCardsElement.style.overflow = '';
547            if (wrapper) {
548                wrapper.style.display = '';
549                wrapper.style.flexWrap = '';
550                wrapper.style.gap = '';
551            }
552            const $section = $(winnerCardsElement).closest('.winner');
553            const winnerSwiper = trackSwiper(new Swiper(winnerCardsElement, {
554                observer: true,
555                observeParents: true,
556                slidesPerView: 'auto',
557                spaceBetween: 12,
558                loop: true,
559                speed: 800,
560                allowTouchMove: true,
561                // Stepwise autoplay (delay 2.5s) so nav-arrow / touch
562                // clicks land in the idle gap between transitions —
563                // delay:0 + chained slideNext blocks user input.
564                autoplay: {
565                    delay: 2500,
566                    disableOnInteraction: false,
567                },
568                navigation: {
569                    nextEl: $section.find('.swiper-button-next')[0],
570                    prevEl: $section.find('.swiper-button-prev')[0],
571                },
572                wrapperClass: 'swiper-wrapper',
573                slideClass: 'swiper-slide',
574            }));
575
576            // Instant pause on hover — Swiper's built-in
577            // pauseOnMouseEnter lets the current transition finish
578            // before pausing, which feels sluggish. Cancelling the
579            // transition and snapping to the current rendered translate
580            // freezes the strip immediately.
581            let winnerPaused = false;
582            const winnerPause = () => {
583                if (winnerPaused) return;
584                winnerPaused = true;
585                const x = winnerSwiper.getTranslate();
586                winnerSwiper.autoplay.stop();
587                winnerSwiper.setTransition(0);
588                winnerSwiper.setTranslate(x);
589            };
590            const winnerResume = () => {
591                if (!winnerPaused) return;
592                winnerPaused = false;
593                // Restore the standard transition and let autoplay
594                // schedule the next stepwise tick after its delay.
595                winnerSwiper.setTransition(winnerSwiper.params.speed);
596                winnerSwiper.autoplay.start();
597            };
598            winnerCardsElement.addEventListener('mouseenter', winnerPause);
599            winnerCardsElement.addEventListener('mouseleave', winnerResume);
600        }
601
602        // Check if Swiper is already initialized on providers swiper
603        const providersElement = document.querySelector('.providers-swiper');
604        if (providersElement && providersElement.swiper) {
605            console.warn('Swiper already initialized on providers swiper, skipping')
606        } else if (providersElement) {
607            new Swiper('.providers-swiper', {
608            slidesPerView: '3',
609            spaceBetween: 10,
610            loop: true,
611            autoplay: {
612                delay: 0
613            },
614            freeMode: true,
615            speed: 5000,
616            direction: 'horizontal',
617            grabCursor: true,
618            breakpoints: {
619                320: {
620                slidesPerView: 3.5,
621                },
622                480: {
623                slidesPerView: 3.5,
624                },
625                768: {
626                slidesPerView: 4.5,
627                },
628                1024: {
629                slidesPerView: 5.5,
630                }
631            }
632        });
633        }
634    }
635
636    function openMobileMenu() {
637        const navbar = $('.navbar')[0];
638        if (navbar) {
639            // Reset transform and transition when opening
640            navbar.style.transform = '';
641            navbar.style.transition = '';
642        }
643        $('.navbar').addClass('active-mobile');
644        $('#mobile-menu-link').addClass('active');
645        $('body').addClass('overflow-hidden');
646    }
647
648    function setupMobileMenuDrag() {
649        const menu = document.getElementById('side-navigation-menu');
650        const puller = document.getElementById('navigation-menu-puller');
651        setupDragHandler(menu, puller, () => closeMenu('mobile'));
652    }
653
654    function initializeMenus() {
655        $(document).on('click', function(e) {
656          if (!$(e.target).closest('.header__notification, #notifications-list').length) {
657            $("#notifications-list").removeClass("active");
658          }
659        });
660
661        // Navbar head toggle
662        $(".navbar__head").off('click').on("click", function () {
663            $(this).toggleClass("active");
664            $(this).next().slideToggle(300);
665        });
666
667        // Burger menu toggle
668        $(".burger").off('click').on("click", function () {
669            $(this).children().toggleClass("active");
670            $(".navbar").toggleClass("active");
671            $("body").toggleClass("fixed");
672            return false;
673        });
674
675        $(document).off('click.menuClose').on("click.menuClose", function (e) {
676            if (!$(e.target).closest(".navbar").length && !$('.navbar').hasClass('active-mobile')) {
677                $(".burger").children().removeClass("active");
678                $(".navbar").removeClass("active");
679                $("body").removeClass("fixed");
680            }
681        });
682
683        // Mobile menu link
684        $('#mobile-menu-link').off('click').on('click', function () {
685            // Close profile menu if it's open
686            $('.profile-menu').removeClass('active');
687
688            // Close social menu if it's open with animation
689            const socialMenu = document.querySelector('.social-menu-navbar');
690            if (socialMenu && socialMenu.classList.contains('active')) {
691                closeMenu('social');
692            }
693
694            if ($('.navbar').hasClass('active-mobile')) {
695                closeMenu('mobile');
696            } else {
697                openMobileMenu();
698            }
699            return false;
700        });
701
702        // Social menu modal - using Stimulus controller now
703        $('#social-menu-modal-link').off('click').on('click', function() {
704            // Close profile menu if it's open
705            $('.profile-menu').removeClass('active');
706
707            // This will be handled by the Stimulus controller
708            return false;
709        });
710
711        // Setup mobile menu drag functionality
712        setupMobileMenuDrag();
713
714
715    }
716
717    function initializeBonusCarousel() {
718      const el = document.getElementById('bonus-carousel');
719      if (!el || el.dataset.init === 'true') return;
720      if (el.swiper) {
721        console.warn('Bonus carousel Swiper already initialized, skipping');
722        return;
723      }
724
725      // Configurable settings with UX-optimized defaults
726      const AUTOPLAY_DELAY = parseInt(el.dataset.delay, 10) || 5000; // 5 seconds for short-text banners
727      const RESUME_AFTER = parseInt(el.dataset.resumeAfter, 10) || 2000; // 2 seconds after interaction
728      const TRANSITION_SPEED = parseInt(el.dataset.speed, 10) || 600; // Smooth transitions
729
730      let resumeTimeout = null;
731      let isHovering = false;
732      let isTouching = false;
733      let isInteracting = false;
734
735      const swiper = new Swiper(el, {
736        wrapperClass: 'swiper-wrapper',
737        slideClass: 'swiper-slide',
738        loop: true,
739        grabCursor: true,
740        slidesPerView: 1,
741        spaceBetween: 0,
742        speed: TRANSITION_SPEED,
743        // Enhanced autoplay configuration for better UX
744        autoplay: {
745          delay: AUTOPLAY_DELAY,
746          disableOnInteraction: false, // Keep autoplay running after manual interaction
747          pauseOnMouseEnter: false, // We handle hover manually for better control
748          stopOnLastSlide: false, // Continue looping
749          reverseDirection: false
750        },
751        pagination: {
752          el: el.querySelector('.swiper-pagination'),
753          clickable: true
754        },
755        navigation: {
756          nextEl: el.querySelector('.swiper-button-next'),
757          prevEl: el.querySelector('.swiper-button-prev')
758        },
759        // Enhanced touch and keyboard support
760        touchRatio: 1,
761        touchAngle: 45,
762        simulateTouch: true,
763        allowTouchMove: true,
764        keyboard: {
765          enabled: true,
766          onlyInViewport: true
767        },
768      });
769
770      function clearResumeTimer() {
771        if (resumeTimeout) {
772          clearTimeout(resumeTimeout);
773          resumeTimeout = null;
774        }
775      }
776
777      function pauseImmediate() {
778        clearResumeTimer();
779        isInteracting = true;
780        if (swiper.autoplay.running) {
781          swiper.autoplay.stop();
782        }
783      }
784
785      function scheduleResume() {
786        clearResumeTimer();
787        resumeTimeout = setTimeout(() => {
788          isInteracting = false;
789          // Only resume if not hovering and not touching
790          if (!isHovering && !isTouching && !isInteracting) {
791            if (!swiper.autoplay.running) {
792              swiper.autoplay.start();
793            }
794          }
795        }, RESUME_AFTER);
796      }
797
798      // Enhanced hover behavior
799      el.addEventListener('mouseenter', () => {
800        isHovering = true;
801        if (swiper.autoplay.running) {
802          swiper.autoplay.stop();
803        }
804        scheduleResume();
805      });
806
807      el.addEventListener('mouseleave', () => {
808        isHovering = false;
809        if (!isTouching && !isInteracting) {
810          scheduleResume();
811        }
812      });
813
814      // Enhanced touch behavior
815      swiper.on('touchStart', () => {
816        isTouching = true;
817        pauseImmediate();
818      });
819
820      swiper.on('touchEnd', () => {
821        isTouching = false;
822        if (!isHovering && !isInteracting) {
823          scheduleResume();
824        }
825      });
826
827      // Enhanced interaction handling for navigation and pagination
828      const navigationButtons = [el.querySelector('.swiper-button-next'), el.querySelector('.swiper-button-prev')];
829      navigationButtons.forEach(btn => {
830        if (btn) {
831          btn.addEventListener('click', () => {
832            pauseImmediate();
833            scheduleResume();
834          });
835        }
836      });
837
838      const pagination = el.querySelector('.swiper-pagination');
839      if (pagination) {
840        pagination.addEventListener('click', (ev) => {
841          if (ev.target.closest('.swiper-pagination-bullet')) {
842            pauseImmediate();
843            scheduleResume();
844          }
845        });
846      }
847
848      // Handle keyboard navigation
849      el.addEventListener('keydown', (ev) => {
850        if (ev.key === 'ArrowLeft' || ev.key === 'ArrowRight') {
851          pauseImmediate();
852          scheduleResume();
853        }
854      });
855
856      // Add autoplay progress indicator
857      swiper.on('autoplayStart', () => {
858        el.classList.add('swiper-autoplay');
859      });
860
861      swiper.on('autoplayStop', () => {
862        el.classList.remove('swiper-autoplay');
863      });
864
865      // Prevent CLS
866      function lockHeightToTallestSlide() {
867        const slides = Array.from(el.querySelectorAll('.swiper-slide'));
868        let maxH = 0;
869        slides.forEach(s => {
870          const h = s.getBoundingClientRect().height;
871          if (h > maxH) maxH = h;
872        });
873        if (maxH > 0) el.style.minHeight = `${Math.ceil(maxH)}px`;
874      }
875      const imgs = Array.from(el.querySelectorAll('img'));
876      if (imgs.length === 0) lockHeightToTallestSlide();
877      else {
878        let remaining = imgs.length;
879        imgs.forEach(img => {
880          if (img.complete) remaining--;
881          else {
882            img.addEventListener('load', () => { remaining--; if (remaining <= 0) lockHeightToTallestSlide(); });
883            img.addEventListener('error', () => { remaining--; if (remaining <= 0) lockHeightToTallestSlide(); });
884          }
885        });
886        if (remaining <= 0) lockHeightToTallestSlide();
887      }
888      window.addEventListener('resize', () => { setTimeout(lockHeightToTallestSlide, 150); });
889
890      el.dataset.init = 'true';
891      el.swiper = swiper;
892    }
893
894
895
896    function initializeSelects() {
897      // Initialize all select dropdowns
898      document.querySelectorAll('.select').forEach(select => {
899        const main = select.querySelector('.select_main');
900        const variants = select.querySelectorAll('.select_variant');
901
902        // Main click handler - toggle this select only
903        main.addEventListener('click', function(e) {
904          e.stopPropagation();
905          const wasActive = select.classList.contains('active');
906
907          // Close all other selects first
908          document.querySelectorAll('.select').forEach(s => {
909            if (s !== select) {
910              s.classList.remove('active');
911            }
912          });
913
914          // Toggle this select
915          select.classList.toggle('active', !wasActive);
916        });
917
918        // Variant selection handlers
919        variants.forEach(variant => {
920          variant.addEventListener('click', function(e) {
921            e.stopPropagation();
922
923            const icon = variant.querySelector('.select_variant_value_icon img');
924            const text = variant.querySelector('.select_variant_value_text');
925            const mainIcon = main.querySelector('.select_main_value_icon img');
926            const mainText = main.querySelector('.select_main_value_text');
927
928            if (icon && mainIcon) {
929              mainIcon.src = icon.src;
930            }
931
932            if (text && mainText) {
933              mainText.textContent = text.textContent;
934            }
935
936            select.querySelectorAll('.select_variant').forEach(v => {
937              v.classList.remove('active');
938            });
939            variant.classList.add('active');
940            select.classList.remove('active');
941          });
942        });
943      });
944    }
945
946    function openModalByHash(hash) {
947        const modalId = MODAL_HASH_MAP[hash];
948        if (!modalId) return;
949
950        const isLoggedIn = document.querySelector('meta[name="player-id"]');
951
952        if (!isLoggedIn) {
953            // Set session variable for auto-open after login
954            fetch('/set_auto_open_modal', {
955                method: 'POST',
956                headers: {
957                    'Content-Type': 'application/json',
958                    'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
959                },
960                body: JSON.stringify({ modal_type: hash })
961            }).catch(err => {
962                console.error('Failed to set auto-open modal:', err);
963            });
964
965            setTimeout(() => {
966                if (typeof $ !== 'undefined' && $.fancybox) {
967                    $.fancybox.open({
968                        src: '#modalsign',
969                        type: 'inline',
970                    });
971                }
972            }, 500);
973        } else {
974            let triggerButton = null;
975
976            if (modalId === 'dailyChallengeModal') {
977                triggerButton = document.querySelector('[data-controller="daily-challenges"][data-action*="openModal"]');
978            } else if (modalId === 'bonusCodeModal') {
979                // Bonus code modal uses Stimulus controller
980                // First, try to find the button with Stimulus controller
981                triggerButton = document.querySelector('[data-controller="bonus-code"][data-action*="open"]');
982
983                if (triggerButton) {
984                    // Button found, click it to trigger Stimulus controller
985                    setTimeout(() => {
986                        triggerButton.click();
987                    }, 500);
988                    return;
989                }
990
991                // If no button found, try to open modal directly using Bootstrap API
992                // This handles the case when modal exists but button is not in DOM
993                setTimeout(() => {
994                    const modalElement = document.getElementById('bonusCodeModal');
995                    if (modalElement && typeof bootstrap !== 'undefined') {
996                        const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
997                        modal.show();
998                    }
999                }, 500);
1000                return;
1001            } else {
1002                triggerButton = document.querySelector(`[data-bs-target="#${modalId}"]`);
1003            }
1004
1005            if (triggerButton) {
1006                setTimeout(() => {
1007                    triggerButton.click();
1008                }, 500);
1009            }
1010        }
1011    }
1012
1013    function initializeListeners(){
1014        if (window.listenersInitialized) return;
1015
1016        window.listenersInitialized = true
1017
1018        window.addEventListener('hashchange', () => {
1019            const newHash = window.location.hash.substring(1);
1020            if (newHash && MODAL_HASH_MAP[newHash]) {
1021                openModalByHash(newHash);
1022            } else if (!newHash) {
1023                const modals = ['dailyChallengeModal', 'leaderboardModal', 'referralStatsModal', 'rakebackModal'];
1024                modals.forEach(modalId => {
1025                    const modalElement = document.getElementById(modalId);
1026                    if (modalElement) {
1027                        const modal = bootstrap.Modal.getInstance(modalElement);
1028                        if (modal) {
1029                            modal.hide();
1030                        }
1031                    }
1032                });
1033            }
1034        });
1035
1036        // Override fullscreen API on mobile devices to prevent true fullscreen for games
1037        const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
1038        const isAndroid = /Android/.test(navigator.userAgent);
1039
1040        if (isIOS || isAndroid) {
1041            const originalRequestFullscreen = HTMLElement.prototype.requestFullscreen;
1042            const originalMozRequestFullScreen = HTMLElement.prototype.mozRequestFullScreen;
1043            const originalWebkitRequestFullscreen = HTMLElement.prototype.webkitRequestFullscreen;
1044            const originalMsRequestFullscreen = HTMLElement.prototype.msRequestFullscreen;
1045
1046            function interceptFullscreen() {
1047                // Check if this is being called on a game iframe or its contents
1048                const gameSection = this.closest('.game');
1049                const isInGameIframe = this.tagName === 'IFRAME' && this.hasAttribute('data-fullscreen-target');
1050
1051                if ((gameSection || isInGameIframe) && window.wildFullscreenController) {
1052                    // Use our mobile fullscreen instead
1053                    window.wildFullscreenController.toggleMobileFullscreen();
1054                    return Promise.resolve();
1055                }
1056
1057                // For non-game elements, use original fullscreen
1058                return originalRequestFullscreen.call(this);
1059            }
1060
1061            // Override all fullscreen methods
1062            HTMLElement.prototype.requestFullscreen = interceptFullscreen;
1063            if (originalMozRequestFullScreen) {
1064                HTMLElement.prototype.mozRequestFullScreen = interceptFullscreen;
1065            }
1066            if (originalWebkitRequestFullscreen) {
1067                HTMLElement.prototype.webkitRequestFullscreen = interceptFullscreen;
1068            }
1069            if (originalMsRequestFullscreen) {
1070                HTMLElement.prototype.msRequestFullscreen = interceptFullscreen;
1071            }
1072        }
1073
1074        window.addEventListener('message', (e) => {
1075            console.log(e.data);
1076
1077            if (e.data && typeof e.data['verification_status'] === 'string' && trustedKycMessageOrigin(e.origin)) {
1078                const status = e.data['verification_status'];
1079
1080                // verification.received (pending review) must not navigate — the entry point has to survive until the verdict.
1081                if ((status === 'verification.accepted' || status === 'verification.declined') && firstKycStatusHandling(status)) {
1082                    const redeemEntry = sessionStorage.getItem('kycEntryPoint') === 'redeem';
1083                    sessionStorage.removeItem('kycEntryPoint');
1084
1085                    if (status === 'verification.accepted' && redeemEntry) {
1086                        sessionStorage.setItem('openRedeemPending', '1');
1087                        Turbo.visit('/me?tab=player&subtab=balance');
1088                    } else {
1089                        Turbo.visit('/me?tab=player&subtab=proof_of_identification');
1090                    }
1091                }
1092            }
1093
1094            // Handle fullscreen requests from game iframes (mobile only for Coinz)
1095            if (e.data && (e.data.type === 'fullscreen' || e.data.action === 'fullscreen' || e.data === 'fullscreen')) {
1096                const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
1097                const isAndroid = /Android/.test(navigator.userAgent);
1098
1099                // Only handle on mobile devices to prevent true fullscreen
1100                if ((isIOS || isAndroid) && window.wildFullscreenController) {
1101                    e.preventDefault();
1102                    e.stopPropagation();
1103
1104                    // Use the mobile fullscreen instead of true fullscreen
1105                    window.wildFullscreenController.toggleMobileFullscreen();
1106                }
1107            }
1108        });
1109
1110        // Close all selects when clicking outside
1111        document.addEventListener('click', function(e) {
1112          const select = e.target.closest('.select');
1113
1114          console.log('clicked', e.target)
1115
1116          // Close mobile menu if clicked outside
1117          if ($('.navbar').hasClass('active-mobile')) {
1118            // Don't close if clicking on modals or their backdrops
1119            const isModalClick = e.target.closest('.fancybox-container') ||
1120                                 e.target.closest('.modal') ||
1121                                 e.target.classList.contains('fancybox-container') ||
1122                                 e.target.classList.contains('modal-backdrop');
1123
1124            if (!e.target.closest('.navbar') &&
1125                !e.target.closest('#mobile-menu-link') &&
1126                !isModalClick) {
1127              closeMenu('mobile');
1128            }
1129          }
1130
1131          // Close social menu if clicked outside
1132          const socialMenu = document.querySelector('.social-menu-navbar');
1133          if (socialMenu && socialMenu.classList.contains('active')) {
1134            // Don't close if clicking on modals or their backdrops
1135            const isModalClick = e.target.closest('.fancybox-container') ||
1136                                 e.target.closest('.modal') ||
1137                                 e.target.classList.contains('fancybox-container') ||
1138                                 e.target.classList.contains('modal-backdrop');
1139
1140            if (!e.target.closest('.social-menu-navbar') &&
1141                !e.target.closest('#social-menu-modal-link') &&
1142                !isModalClick) {
1143              closeMenu('social');
1144            }
1145          }
1146
1147          // Handle profile menu toggle and close
1148          if (e.target.closest('.header__profile')) {
1149            // Toggle profile menu on profile button click
1150            $(".profile-menu").toggleClass("active");
1151          } else if (e.target.closest('.switch') || e.target.closest('#checkbox2')) {
1152            // Don't close profile menu when clicking on theme switch
1153            // Do nothing, keep menu open
1154          } else if ($('.profile-menu').hasClass('active') && !e.target.closest('.profile-menu')) {
1155            // Close profile menu if clicked outside
1156            $('.profile-menu').removeClass('active');
1157          }
1158
1159          // If clicked outside any select, close all
1160          if (!select) {
1161            document.querySelectorAll('.select').forEach(s => {
1162              s.classList.remove('active');
1163            });
1164          }
1165          // If clicked on a select_main, let its handler manage the toggle
1166          else if (e.target.closest('.select_main')) {
1167            return; // Let the select_main click handler deal with it
1168          }
1169          // If clicked elsewhere in an open select (but not on select_main)
1170          else {
1171            document.querySelectorAll('.select').forEach(s => {
1172              if (s !== select) {
1173                s.classList.remove('active');
1174              }
1175            });
1176          }
1177        });
1178
1179        // Listen for successful deposit events from PaymentIQ cashier
1180        document.addEventListener('deposit', function(e) {
1181            handleDepositSuccess(e.detail);
1182        });
1183
1184        document.addEventListener('first_time_deposit', function(e) {
1185            handleDepositSuccess(e.detail);
1186        });
1187
1188        function handleDepositSuccess(detail) {
1189            // Close the modal
1190            if (typeof $.fancybox !== 'undefined') {
1191                $.fancybox.close();
1192            }
1193
1194            // Handle package_shop context redirect
1195            if (window.purchaseContext === 'package_shop') {
1196                setTimeout(() => {
1197                    window.location.href = '/en';
1198                }, 1500);
1199                return;
1200            }
1201
1202            // Only refresh if path doesn't contain "game" keyword
1203            const currentPath = window.location.pathname.toLowerCase();
1204            if (!currentPath.includes('game')) {
1205                setTimeout(() => {
1206                    if (typeof Turbo !== 'undefined') {
1207                        Turbo.visit(window.location.href, { action: 'replace' });
1208                    } else {
1209                        window.location.reload();
1210                    }
1211                }, 1500);
1212            }
1213        }
1214
1215        document.addEventListener('click', function(e) {
1216            // Handle .open-deposit-modal clicks
1217            const depositModalBtn = e.target.closest('.open-deposit-modal');
1218            if (depositModalBtn) {
1219                e.preventDefault();
1220                e.stopPropagation();
1221
1222                const packageId = depositModalBtn.dataset.packageId;
1223                const bonusId = depositModalBtn.dataset.bonusId;
1224                const pair = depositModalBtn.dataset.pair;
1225
1226                window.purchaseContext = window.location.pathname.includes('/purchases') ? 'package_shop' : 'in_game';
1227
1228                // Check if Coinflow cashier is enabled
1229                if (document.querySelector('[data-controller="coinflow-cashier"]')) {
1230                    window.dispatchEvent(new CustomEvent('open-coinflow-cashier', { detail: { packageId: packageId, bonusId: bonusId, pair: pair } }));
1231                    return;
1232                }
1233
1234                // Check if Fluid cashier is enabled
1235                if (document.querySelector('[data-controller="fluid-widget"]')) {
1236                    window.dispatchEvent(new CustomEvent('open-fluid-cashier', { detail: { packageId: packageId, pair: pair } }));
1237                    return;
1238                }
1239
1240                const url = `/en/deposits/new_fiat?coins_package_id=${packageId}&bonus_id=${bonusId}&render_layout=false`;
1241
1242                $.fancybox.open({
1243                    src: url,
1244                    type: 'ajax',
1245                    opts: {
1246                        afterShow: function(instance, current) {
1247                            console.log('Modal loaded successfully!');
1248                        },
1249                        afterClose: function() {
1250                            if (typeof xtremepush === 'function') {
1251                                xtremepush('event', 'purchase_popup_closed');
1252                            }
1253                        }
1254                    }
1255                });
1256                return;
1257            }
1258
1259            const referralButton = e.target.closest('#copyReferralButton');
1260            if (referralButton) {
1261                const copyText = document.getElementById("referralLink");
1262
1263                navigator.clipboard.writeText(copyText.value).then(() => {
1264                    const button = e.target;
1265                    const originalText = button.textContent;
1266                    button.textContent = 'Copied!';
1267
1268                    setTimeout(() => {
1269                        button.textContent = originalText;
1270                    }, 2000);
1271                }).catch(err => {
1272                    console.error('Failed to copy text: ', err);
1273                });
1274            }
1275
1276            // Handle #buy-button clicks
1277            const buyButton = e.target.closest('#buy-button');
1278            if (buyButton) {
1279                e.preventDefault();
1280                e.stopPropagation();
1281
1282                const amount = document.getElementById('amount-input')?.value;
1283                if (!amount) return;
1284
1285                const url = `/en/deposits/new_fiat?amount=${amount}&render_layout=false&build_package=true`;
1286
1287                $.fancybox.open({
1288                    src: url,
1289                    type: 'ajax',
1290                    opts: {
1291                        afterShow: function(instance, current) {
1292                            console.log('Modal loaded successfully!');
1293                        }
1294                    }
1295                });
1296                return;
1297            }
1298
1299            // Handle [data-fancybox] clicks
1300            const fancyboxTrigger = e.target.closest('[data-fancybox]');
1301            if (fancyboxTrigger) {
1302                console.log('fancybox111')
1303                e.preventDefault();
1304                e.stopPropagation();
1305
1306                const targetModal = fancyboxTrigger.getAttribute('href');
1307                const fancyboxOptions = {
1308                    src: targetModal,
1309                    type: "inline"
1310                };
1311
1312                if (targetModal === '#modalreg') {
1313                    fancyboxOptions.afterClose = function() {
1314                        window.dispatchEvent(new CustomEvent('registrationModalClosed'));
1315                    };
1316                }
1317
1318                if ($.fancybox.getInstance()) {
1319                    $.fancybox.close();
1320
1321                    setTimeout(() => {
1322                        $.fancybox.open(fancyboxOptions);
1323                    }, 300);
1324                } else {
1325                    $.fancybox.open(fancyboxOptions);
1326                }
1327                return;
1328            }
1329        });
1330    }
1331
1332    initializeListeners();
1333
1334    Object.entries(MODAL_HASH_MAP).forEach(([hashKey, modalId]) => {
1335        const modalElement = document.getElementById(modalId);
1336        if (modalElement && !modalElement.dataset.hashListenerAttached) {
1337            modalElement.dataset.hashListenerAttached = 'true';
1338
1339            modalElement.addEventListener('shown.bs.modal', function() {
1340                console.log(`Modal shown: ${modalId}, adding hash: #${hashKey}`);
1341                if (window.location.hash !== `#${hashKey}`) {
1342                    history.pushState(null, null, `#${hashKey}`);
1343                }
1344            });
1345
1346            modalElement.addEventListener('hidden.bs.modal', function() {
1347                console.log(`Modal hidden: ${modalId}, removing hash`);
1348                if (window.location.hash === `#${hashKey}`) {
1349                    history.pushState(null, null, window.location.pathname + window.location.search);
1350                }
1351            });
1352        }
1353    });
1354
1355    const hash = window.location.hash.substring(1);
1356    if (hash && MODAL_HASH_MAP[hash]) {
1357        openModalByHash(hash);
1358    }
1359
1360    // Check for auto-open modal flags from session
1361    function checkAutoOpenModals() {
1362        const autoOpenDailyquest = document.querySelector('meta[name="auto-open-dailyquest-modal"]')?.content === 'true';
1363        const autoOpenRaces = document.querySelector('meta[name="auto-open-races-modal"]')?.content === 'true';
1364        const autoOpenRefer = document.querySelector('meta[name="auto-open-refer-modal"]')?.content === 'true';
1365        const autoOpenBonusCode = document.querySelector('meta[name="auto-open-bonus-code
1365-modal"]')?.content === 'true';
1366        const autoOpenCoinzback = document.querySelector('meta[name="auto-open-coinzback-modal"]')?.content === 'true';
1367
1368        if (autoOpenDailyquest) {
1369            setTimeout(() => {
1370                openModalByHash('dailyquest');
1371            }, 500);
1372        } else if (autoOpenRaces) {
1373            setTimeout(() => {
1374                openModalByHash('races');
1375            }, 500);
1376        } else if (autoOpenRefer) {
1377            setTimeout(() => {
1378                openModalByHash('refer');
1379            }, 500);
1380        } else if (autoOpenBonusCode) {
1381            setTimeout(() => {
1382                openModalByHash('bonus_code');
1383            }, 500);
1384        } else if (autoOpenCoinzback) {
1385            setTimeout(() => {
1386                openModalByHash(RAKEBACK_HASH);
1387            }, 500);
1388        }
1389    }
1390
1391    // Check auto-open modals after page load
1392    checkAutoOpenModals();
1393
1394    // The verified Redeem trigger renders only after the Shufti webhook lands — poll quietly, reload once it has.
1395    if (sessionStorage.getItem('openRedeemPending') === '1') {
1396        const redeemTrigger = document.querySelector('[data-redeem-trigger]');
1397
1398        if (redeemTrigger) {
1399            sessionStorage.removeItem('openRedeemPending');
1400            setTimeout(() => {
1401                redeemTrigger.click();
1402            }, 500);
1403        } else {
1404            const pollUrl = window.location.href;
1405            let attempts = 0;
1406
1407            const pollForRedeemTrigger = () => {
1408                if (window.location.href !== pollUrl) return;
1409
1410                fetch(pollUrl)
1411                    .then((response) => response.text())
1412                    .then((html) => {
1413                        if (html.includes('data-redeem-trigger')) {
1414                            Turbo.visit(pollUrl);
1415                        } else if (++attempts < 5) {
1416                            setTimeout(pollForRedeemTrigger, 2000);
1417                        } else {
1418                            sessionStorage.removeItem('openRedeemPending');
1419                        }
1420                    })
1421                    .catch(() => sessionStorage.removeItem('openRedeemPending'));
1422            };
1423
1424            setTimeout(pollForRedeemTrigger, 2000);
1425        }
1426    }
1427
1428    // Handle ?modal= query parameter for registration and signin
1429    const urlParams = new URLSearchParams(window.location.search);
1430    const modalParam = urlParams.get('modal');
1431    if (modalParam && (modalParam === 'registration' || modalParam === 'signin')) {
1432        const modalId = modalParam === 'registration' ? '#modalreg' : '#modalsign';
1433        setTimeout(() => {
1434            if (typeof $ !== 'undefined' && $.fancybox) {
1435                if (modalId === '#modalreg') {
1436                    $.fancybox.open({
1437                        src: modalId,
1438                        type: 'inline',
1439                        opts: {
1440                            afterClose: function() {
1441                                window.dispatchEvent(new CustomEvent('registrationModalClosed'));
1442                            }
1443                        }
1444                    });
1445                } else {
1446                    $.fancybox.open({
1447                        src: modalId,
1448                        type: 'inline'
1449                    });
1450                }
1451            }
1452        }, 500);
1453
1454        // Remove modal param from URL after opening
1455        urlParams.delete('modal');
1456        const newUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : '');
1457        window.history.replaceState({}, document.title, newUrl);
1458    }
1459
1460    initializeSelects();
1461
1462    initializeSliders();
1463    // Exposed so Turbo-Stream navigations (store_navigation_controller replaces #games without a
1464    // turbo:load) can re-init game-row swipers inserted into the new content. Idempotent.
1465    window.wildInitializeSliders = initializeSliders;
1466    initializeMenus();
1467    initializeBonusCarousel();
1468})
1469
1470$(document).on("ready turbo:load turbo:frame-render", function() {
1471    const sections = document.querySelectorAll('.account__content--second > div');
1472
1473    // Initialize edit buttons for inputs
1474    document.querySelectorAll('.account__input--edit').forEach(editButton => {
1475      const freshEditButton = editButton.parentNode.querySelector('.account__input--edit');
1476
1477      freshEditButton.addEventListener('click', function() {
1478        const inputField = this.previousElementSibling;
1479
1480        if (inputField && inputField.matches('input, textarea')) {
1481          inputField.readOnly = false;
1482          inputField.classList.toggle('active');
1483          this.style.display = 'none';
1484          inputField.focus();
1485
1486          // Optional: Handle when input loses focus
1487          const handleBlur = () => {
1488            inputField.readOnly = true;
1489            inputField.classList.remove('active');
1490            freshEditButton.style.display = '';
1491            inputField.removeEventListener('blur', handleBlur);
1492          };
1493
1494          inputField.addEventListener('blur', handleBlur);
1495        }
1496      });
1497    });
1498})
1499
1500// workaround for redemption form, for some reason events not fired on mobiles
1501window.addEventListener('touchend', function (e) {
1502  const btn = e.target.closest('#modalwithdrawal .withdrawal_button[type="submit"]');
1503  console.log('tochend fired', btn)
1504  if (!btn) return;
1505
1506  e.preventDefault();
1507  e.stopPropagation();
1508
1509  const form = btn.form;
1510  if (form?.requestSubmit) form.requestSubmit();
1511  else if (form) form.submit();
1512}, { capture: true, passive: false });

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.