PageSourceSearch

https://aksesonbola.com/js/header.menu.vue.js?v=1782531230007

js aksesonbola.com collected 2026-10-05 21:32:46 UTC 126,633 bytes, 2,622 lines download raw bytes

1// Last Updated: 2026.04.16
2// Util functions part
3var groupBy = function (xs, key) {
4    return xs.reduce(function (rv, x) {
5        (rv[x[key].trim()] = rv[x[key].trim()] || []).push(x);
6        return rv;
7    }, {});
8};
9function extractTimestampFromString(inputString) {
10    var regex = /\/Date\((\d+)\)\//;
11    var match = regex.exec(inputString);
12    if (match && match[1]) return match[1];
13    return null;
14}
15function extractValueFromQueryString(inputString) {
16    var regex = /v=([^&]+)/;
17    var match = regex.exec(inputString);
18    if (match && match[1]) return match[1];
19    else return 0;
20}
21function getTimestampServerCache() {
22    var scriptElements = document.getElementsByTagName('script');
23    for (var i = 0; i < scriptElements.length; i++) {
24        var scriptSrc = scriptElements[i].getAttribute('src');
25        if (scriptSrc && scriptSrc.indexOf('header.menu.vue') > -1)
26            return extractValueFromQueryString(scriptSrc);
27    }
28    return 0;
29}
30function getQueryParam(paramName) {
31    var url_string;
32    if (isHeaderPage()) {
33        try {
34            url_string = window.parent.location.href;
35        } catch {
36            url_string = window.location.href;
37        }
38    } else {
39        url_string = window.location.href;
40    }
41    var url = new URL(url_string);
42    return url.searchParams.get(paramName);
43}
44let addPrefix = (variableName) =>
45    isNaN(variableName.substring(0, 1)) ? '' : '_';
46
47function changeHeaderHeight(height) {
48    try {
49        window.parent.document.getElementsByClassName('divHeader')[0].style = 'height:' + height + 'px';
50    }
51    catch (err) {
52        console.log(err)
53    }
54}
55var isMobileDevice = function () {
56    var check = false;
57    (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window.opera);
58    return check;
59}
60let ligaSbSportSubmenuNameTW = '';
61function isLigaSbName(gameName, ligaSbSportSubmenuName) {
62    return gameName === ligaSbSportSubmenuName
63}
64
65const latinWords = ['sportsbooks', 'sportsbook', 'sportbooks', 'sportbook', 'sports', 'sport', 'bola'];
66const unicodeWords = [
67    '\u4F53\u80B2\u7C7B',
68    '\u0E01\u0E35\u0E2C\u0E32',
69    'Th\u1EC3 thao',
70    '\uC2A4\uD3EC\uCE20'
71];
72
73const latinWordPattern = latinWords.join('|');
74const unicodeWordPattern = unicodeWords.join('|');
75
76const latinRegex = new RegExp(`\\b(${latinWordPattern})\\b`, 'iu');
77const unicodeRegex = new RegExp(`(${unicodeWordPattern})`, 'iu');
78const latinReplaceRegex = new RegExp(`\\b(${latinWordPattern}
78)(?:\\s*[123])?\\b`, 'giu');
79const unicodeReplaceRegex = new RegExp(`(${unicodeWordPattern})(?:\\s*[123])?`, 'giu');
80
81function replaceSPORTLabelHtml(html, sportBookType) {
82    const sportNumber = `${sportBookType || ''}`.trim();
83    const latinLabel = sportNumber ? `SPORT ${sportNumber}` : 'SPORT';
84
85    return (html || '')
86        .replace(latinReplaceRegex, latinLabel)
87        .replace(unicodeReplaceRegex, function (_, unicodeLabel) {
88            return sportNumber ? `${unicodeLabel} ${sportNumber}` : unicodeLabel;
89        })
90        .replace(/\s*\u25BC\s*/g, '');
91}
92
93function getSPORTByText() {
94    var sportItems = $(mountEl).find('a').filter(function () {
95        var text = $(this).text();
96        return latinRegex.test(text) || unicodeRegex.test(text);
97    }).map(function () {
98        var parentLi = $(this).closest('li');
99        return parentLi.length ? parentLi : $(this);
100    }).get();
101
102    if (sportItems.length > 0) {
103        sportItems.forEach(function (item) {
104            console.log($(item)[0].outerHTML);
105        });
106    } else {
107        console.log("No <a> tags found with relevant SPORT text.");
108    }
109
110    return sportItems;
111}
112
113function getSPORTByHref() {
114    return $(mountEl).find('a').filter(function () {
115        return $(this).attr('href') && $(this).attr('href').toLowerCase().includes('_view/rmodds1.aspx');
116    }).map(function () {
117        var parentLi = $(this).closest('li');
118        return parentLi.length ? parentLi : $(this);
119    }).get();
120}
121
122function getNonSPORTLinks() {
123    const excludeFiles = ['body.aspx', 'lobbygames.aspx'];
124
125    return $(document).find('a').filter(function () {
126        const href = $(this).attr('href');
127        if (!href) return false;
128        const hrefLower = href.toLowerCase();
129        return !excludeFiles.some(ex => hrefLower.indexOf(ex) !== -1);
130    }).map(function () {
131        const parentLi = $(this).closest('li');
132        return parentLi.length ? parentLi : $(this);
133    }).get();
134}
135
136function getSportBookTypeList() {
137    return (localStorage.getItem('sportBookTypeList') || '0').trim();
138}
139function parseSportList() {
140    return getSportBookTypeList().split(',').map(s => s.trim()).filter(Boolean);
141}
142function isSportBookAll() {
143    return parseSportList().includes('0');
144}
145function canAccessSport(num) {
146    return isSportBookAll() || parseSportList().includes(String(num));
147}
148function isSportOnly(num) {
149    const list = parseSportList();
150    return !list.includes('0') && list.includes(String(num)) && list.length === 1;
151}
152function getSingleSportNumber() {
153    const list = parseSportList();
154    if (!list.includes('0') && list.length === 1) return +list[0];
155    return 0;
156}
157// Track which sport the user is currently using (updated on sport menu click)
158function setSelectedSport(num) {
159    sessionStorage.setItem('sportBookSelected', String(num));
160}
161
162// Returns '&lpnl=lsb' for Sport2, '&lpnl=omsb' for Sport3, '' for Sport1
163// Reads sportBookSelected (session), falls back to sportBookDef (default config)
164function getLpnlParam() {
165    const selected = +(sessionStorage.getItem('sportBookSelected') || localStorage.getItem('sportBookDef')) || 2;
166    if (selected === 3) return '&lpnl=omsb';
167    if (selected === 2) return '&lpnl=lsb';
168    return '';
169}
170
171function filterMenuDataBySportBookType(menuData, sportBookTypeList) {
172    if (!Array.isArray(menuData)) return menuData;
173    const list = (sportBookTypeList || '0').split(',').map(s => s.trim());
174    if (list.includes('0')) return menuData;
175    const canSport1 = list.includes('1');
176    const canSport2 = list.includes('2');
177    const canSport3 = list.includes('3');
178    return menuData.filter(record => {
179        const gameType = (record.GameType || '').trim().toUpperCase();
180        const isLigaSB = record.IsLigaSB === true;
181        if (gameType === 'AFBSB') return isLigaSB ? canSport2 : canSport1;
182        if (gameType === 'LIGASB') return canSport2;
183        if (gameType === 'OMSB') return canSport3;
184        return true;
185    });
186}
187
188function filterMenuMapBySportBookType(menuMap, sportBookTypeList) {
189    if (!menuMap || typeof menuMap !== 'object') return menuMap;
190    const list = (sportBookTypeList || '0').split(',').map(s => s.trim());
191    if (list.includes('0')) return menuMap;
192    return Object.keys(menuMap).reduce((result, key) => {
193        const filteredRecords = filterMenuDataBySportBookType(menuMap[key], sportBookTypeList);
194        if (Array.isArray(filteredRecords) && filteredRecords.length > 0)
195            result[key] = filteredRecords;
196        return result;
197    }, {});
198}
199
200function syncMenuMap(target, source) {
201    // Remove keys from target not in source
202    Object.keys(target).forEach(key => {
203        if (!(key in source))
204            delete target[key];
205    });
206    // If source has keys not yet in target, those would be appended at end (wrong position).
207    // Rebuild target in source key order only when new keys are detected.
208    if (Object.keys(source).some(key => !(key in target))) {
209        Object.keys(target).forEach(key => delete target[key]);
210    }
211    Object.assign(target, source);
212}
213
214function isNewGameLigaSB() {
215    return JSON.parse(localStorage.getItem('menuMap_user') || '{}')?.['LIGASB']?.[0]?.IsNewGame ?? false;
216}
217
218// ╔═══════════════════════════════════════════════════════════════════════════╗
219// ║                     HEADER MENU SCROLL (SWIPER)                          ║
220// ╠═══════════════════════════════════════════════════════════════════════════╣
221// ║  § 1  Configuration  – constants, themes, CTId groups, extra CSS         ║
222// ║  § 2  CSS injection  – ensureHeaderMenuScrollStyle()                     ║
223// ║  § 3  Helpers        – CTId, theme, host, viewport, DOM utils            ║
224// ║  § 4  Core           – markup, init, schedule                            ║
225// ╚═══════════════════════════════════════════════════════════════════════════╝
226
227// ── § 1  Configuration ───────────────────────────────────────────────────────
228
229const HEADER_MENU_SCROLL_STYLE_ID = 'header-menu-scroll-style';
230const HEADER_MENU_SCROLL_HOST_CLASS = 'header-menu-scroll-host';
231const HEADER_MENU_SCROLL_ACTIVE_CLASS = 'header-menu-scroll-active';
232
233// CTIds that will NOT activate scroll menu on header page.
234const HEADER_MENU_SCROLL_DISABLED_CTIDS = [
235    47, 53, 60, 75, 88, 89,115,
236    159, 168, 172, 173, 174, 185, 
237    193, 197, 201, 203, 208, 
238];
239
240// CTIds that will NOT activate scroll menu on default page.
241const HEADER_MENU_SCROLL_DEFAULT_PAGE_DISABLED_CTIDS = [
242    47, 60, 75, 80, 88, 89, 109,
243    159, 168, 172, 173, 174, 181, 182, 183,
244    185, 193, 198, 200, 203, 208, 217, 218, 221,
245    222
246];
247
248// Named button themes – defines prev/next button appearance per theme.
249// Mirrors GetThemeColor() in Util.LeftPanel.cs.
250const HEADER_MENU_SCROLL_THEMES = {
251    'blue': {
252        color: '#fff',
253        background: 'rgba(10, 50, 120, .65)',
254        border: '1px solid rgba(255, 255, 255, .18)',
255    },
256    'blue_hana': {
257        color: '#fff',
258        background: 'rgba(15, 60, 140, .65)',
259        border: '1px solid rgba(255, 255, 255, .18)',
260    },
261    'gray': {
262        color: '#fff',
263        background: 'rgba(60, 65, 75, .65)',
264        border: '1px solid rgba(255, 255, 255, .18)',
265    },
266    'green': {
267        color: '#fff',
268        background: 'rgba(15, 75, 35, .65)',
269        border: '1px solid rgba(255, 255, 255, .18)',
270    },
271    'orange': {
272        color: '#fff',
273        background: 'rgba(150, 65, 5, .7)',
274        border: '1px solid rgba(255, 255, 255, .18)',
275    },
276    'purple': {
277        color: '#fff',
278        background: 'rgba(70, 20, 110, .65)',
279        border: '1px solid rgba(255, 255, 255, .18)',
280    },
281    'red': {
282        color: '#fff',
283        background: 'rgba(130, 20, 20, .65)',
284        border: '1px solid rgba(255, 255, 255, .18)',
285    },
286    'yellow': {
287        color: '#fff',
288        background: 'rgba(120, 90, 5, .7)',
289        border: '1px solid rgba(255, 220, 50, .35)',
290    },
291    'other': {
292        color: '#fff',
293        background: 'rgba(3, 35, 52, .46)',
294        border: '1px solid rgba(255, 255, 255, .14)',
295    },
296};
297
298// Theme groups – mirrors switch cases in GetThemeColor() (Util.LeftPanel.cs).
299// To add/remove a CTId: edit the array for the relevant theme below.
300const HEADER_MENU_SCROLL_THEME_GROUPS = {
301    'blue': [
302        13, 50, 51, 52, 54, 55, 57, 65, 67, 68, 72, 73, 74, 77, 81, 82, 83,
303        87, 90, 93, 94, 95, 96, 98, 99, 100, 102, 104, 108, 111, 112, 113,
304        116, 118, 120, 122, 125, 127, 130, 131, 132, 133, 135, 139, 140, 143,
305        144, 146, 148, 149, 158, 159, 167, 171, 177, 178, 179, 180, 183, 185,
306        190, 196, 197, 199, 200, 203, 208, 209, 211, 212, 214, 217, 218, 219,
307        220, 225, 226, 229, 230, 233, 238, 239, 242, 246, 249, 250, 252, 258,
308        259, 260, 261, 265, 271, 272, 273, 278, 286, 292, 297, 299, 302, 303,
309        305, 307, 311, 312, 317, 318, 322, 324, 325, 328, 329,
310    ],
311    'blue_hana': [
312        47, 49, 60, 287, 301,
313    ],
314    'gray': [
315        76, 84, 91, 101, 114, 168, 195, 210, 234,
316    ],
317    'green': [
318        63, 69, 78, 109, 115, 117, 124, 126, 129, 134, 137, 138, 141, 147,
319        161, 163, 165, 169, 170, 174, 186, 187, 216, 223, 241, 243, 245, 247,
320        251, 253, 254, 255, 263, 264, 266, 267, 274, 276, 277, 279, 280, 285,
321        288, 290, 293, 296, 300, 304, 306, 316, 320, 331,
322    ],
323    'orange': [
324        59, 237, 256, 275, 327,
325    ],
326    'purple': [
327        75, 107, 145, 151, 181, 202, 207, 270, 281, 283, 289, 295, 315,
328    ],
329    'red': [
330        48, 85, 86, 92, 105, 128, 142, 150, 153, 154, 156, 157, 166, 176,
331        182, 192, 193, 221, 235, 236, 240, 244, 268, 284, 298, 308, 313, 319,
332        321, 323, 326,
333    ],
334    'yellow': [
335        53, 56, 58, 62, 64, 66, 70, 80, 88, 89, 103, 106, 110, 119, 121, 123,
336        136, 152, 155, 162, 164, 172, 173, 175, 184, 188, 189, 191, 194, 198, 201,
337        204, 205, 206, 213, 215, 222, 224, 227, 228, 231, 232, 248, 257, 262,
338        269, 282, 291, 294, 309, 310, 314, 330,
339    ],
340};
341
342// Build CTId → theme name map from groups above (do not edit manually).
343const HEADER_MENU_SCROLL_CTID_THEME_MAP = Object.entries(HEADER_MENU_SCROLL_THEME_GROUPS)
344    .reduce(function (map, entry) {
345        entry[1].forEach(function (id) { map[id] = entry[0]; });
346        return map;
347    }, {});
348
349// Fallback theme for CTIds not listed in any group (mirrors 'other' in GetThemeColor()).
350const HEADER_MENU_SCROLL_DEFAULT_THEME = 'other';
351
352// Extra scoped CSS applied per theme group or specific CTIds.
353// Each entry:
354//   themes – (optional) array of theme names this applies to
355//   CTIds  – (optional) array of specific CTId numbers
356//   css    – CSS string; use {HOST} as placeholder for the scoped host selector
357//            e.g. '{HOST} #main-menu > li { display: flex !important; }'
358// Matching: a CTId matches if it appears in CTIds OR its resolved theme appears in themes (OR logic).
359const HEADER_MENU_SCROLL_EXTRA_CSS = [
360    // Example – flex + vertically-centred li for specific CTIds:
361    // {
362    //     CTIds: [13, 55],
363    //     css: `{HOST} #main-menu > li { display: flex !important; align-items: center !important; }`,
364    // },
365    // Example – apply to an entire theme group:
366    // {
367    //     themes: ['blue', 'green'],
368    //     css: `{HOST} #main-menu > li { display: flex !important; align-items: center !important; }`,
369    // },
370    {
371        CTIds: [184],
372        css: `{HOST} #main-menu > li { display: flex !important; align-items: center !important; }
373                #main-menu{max-width:unset !important}`,
374    }
375];
376
377// ── § 2  CSS injection ───────────────────────────────────────────────────────
378
379let headerMenuScrollInitTimer = 0;
380
381function ensureHeaderMenuScrollStyle() {
382    if (document.getElementById(HEADER_MENU_SCROLL_STYLE_ID))
383        return;
384
385    const style = document.createElement('style');
386    style.id = HEADER_MENU_SCROLL_STYLE_ID;
387    style.textContent = `
388        .${HEADER_MENU_SCROLL_HOST_CLASS} {
389            position: relative;
390            overflow: visible;
391            box-sizing: border-box;
392        }
393        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__viewport {
394            position: relative;
395            width: 100%;
396            overflow-x: auto;
397            overflow-y: hidden;
398            box-sizing: border-box;
399            scrollbar-width: none;
400            -ms-overflow-style: none;
401            touch-action: pan-x;
402        }
403        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__viewport::-webkit-scrollbar {
404            display: none;
405        }
406        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__list {
407            display: inline-flex !important;
408            flex-wrap: nowrap !important;
409            justify-content: space-between !important;
410            align-items: stretch !important;
411            width: max-content !important;
412            min-width: 100% !important;
413            margin: 0 !important;
414            padding: 0 !important;
415            white-space: nowrap !important;
416        }
417        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__list > * {
418            float: none !important;
419            flex: 0 0 auto !important;
420        }
421        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__list > * > a,
422        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__list > * a {
423            white-space: nowrap !important;
424        }
425        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__button {
426            position: absolute;
427            top: 50%;
428            transform: translateY(-50%);
429            width: 26px;
430            height: 26px;
431            border-radius: 999px;
432            padding: 0;
433            display: none;
434            align-items: center;
435            justify-content: center;
436            background: transparent;
437            color: var(--hmsc-btn-color, #fff);
438            border: var(--hmsc-btn-border, 1px solid rgba(255, 255, 255, .14));
439            font-size: 20px;
440            line-height: 1;
441            cursor: pointer;
442            z-index: 2147483647;
443            box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
444            backdrop-filter: blur(2px);
445            -webkit-backdrop-filter: blur(2px);
446        }
447        .${HEADER_MENU_SCROLL_HOST_CLASS}.${HEADER_MENU_SCROLL_ACTIVE_CLASS} .header-menu-scroll__button {
448            display: inline-flex;
449        }
450        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__button:disabled {
451            opacity: .35;
452            cursor: default;
453            pointer-events: none;
454        }
455        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__button--prev {
456            left: 6px;
457        }
458        .${HEADER_MENU_SCROLL_HOST_CLASS} .header-menu-scroll__button--next {
459            right: 6px;
460        }
461    `;
462
463    document.head.appendChild(style);
464}
465
466// ── § 3  Helpers ─────────────────────────────────────────────────────────────
467
468function getScrollMenuCTId() {
469    const raw = localStorage.getItem('CTId') || sessionStorage.getItem('CTId') || '';
470    const id = +raw;
471    return isNaN(id) ? 0 : id;
472}
473
474// Header page: enabled by default, opt-out via DISABLED_CTIDS.
475// CTId null/0 → enable (localStorage not yet written → don't block).
476function shouldHeaderPageUseScroll() {
477    const CTId = getScrollMenuCTId();
478    return !HEADER_MENU_SCROLL_DISABLED_CTIDS.includes(CTId);
479}
480
481// Default page: enabled by default, opt-out via DEFAULT_PAGE_DISABLED_CTIDS.
482// CTId null/0 → enable.
483function shouldDefaultPageUseScroll() {
484    const CTId = getScrollMenuCTId();
485    return !HEADER_MENU_SCROLL_DEFAULT_PAGE_DISABLED_CTIDS.includes(CTId);
486}
487
488function shouldUseHeaderMenuScroll(menuEl) {
489    const isMountEl = typeof mountEl !== 'undefined' && menuEl === mountEl;
490    if (!(menuEl && (menuEl.id === 'main-menu' || isMountEl) && menuEl.parentElement))
491        return false;
492    return isHeaderPage() ? shouldHeaderPageUseScroll() : shouldDefaultPageUseScroll();
493}
494
495function getHeaderMenuScrollButtonTheme(CTId) {
496    const themeName = HEADER_MENU_SCROLL_CTID_THEME_MAP[CTId] || HEADER_MENU_SCROLL_DEFAULT_THEME;
497    return HEADER_MENU_SCROLL_THEMES[themeName] || HEADER_MENU_SCROLL_THEMES[HEADER_MENU_SCROLL_DEFAULT_THEME] || {};
498}
499
500// Applies the per-CTId button theme by setting CSS custom properties on the host element.
501// Also stamps data-hmsc-theme / data-hmsc-ctid for use by extra CSS overrides.
502function applyHeaderMenuScrollTheme(hostEl, CTId) {
503    if (!hostEl) return;
504    const theme = getHeaderMenuScrollButtonTheme(CTId);
505    if (theme.color) hostEl.style.setProperty('--hmsc-btn-color', theme.color);
506    if (theme.background) hostEl.style.setProperty('--hmsc-btn-bg', theme.background);
507    if (theme.border) hostEl.style.setProperty('--hmsc-btn-border', theme.border);
508    hostEl.setAttribute('data-hmsc-theme', HEADER_MENU_SCROLL_CTID_THEME_MAP[CTId] || HEADER_MENU_SCROLL_DEFAULT_THEME);
509    hostEl.setAttribute('data-hmsc-ctid', String(CTId));
510}
511
512// Injects a <style> block with extra scoped CSS rules that match the current CTId/theme.
513// Rules from HEADER_MENU_SCROLL_EXTRA_CSS are scoped via the host element's data-hmsc-ctid attribute.
514function applyHeaderMenuScrollExtraCSS(hostEl, CTId) {
515    if (!hostEl || !HEADER_MENU_SCROLL_EXTRA_CSS.length) return;
516    const resolvedTheme = HEADER_MENU_SCROLL_CTID_THEME_MAP[CTId] || HEADER_MENU_SCROLL_DEFAULT_THEME;
517    const hostSel = '.' + HEADER_MENU_SCROLL_HOST_CLASS + '[data-hmsc-ctid="' + CTId + '"]';
518    const cssBlocks = HEADER_MENU_SCROLL_EXTRA_CSS
519        .filter(function (entry) {
520            const inCTIds = Array.isArray(entry.CTIds) && entry.CTIds.includes(CTId);
521            const inThemes = Array.isArray(entry.themes) && entry.themes.includes(resolvedTheme);
522            return inCTIds || inThemes;
523        })
524        .map(function (entry) { return (entry.css || '').replace(/\{HOST\}/g, hostSel); })
525        .join('\n');
526    if (!cssBlocks) return;
527    const styleId = 'header-menu-scroll-extra-style';
528    let styleEl = document.getElementById(styleId);
529    if (!styleEl) {
530        styleEl = document.createElement('style');
531        styleEl.id = styleId;
532        document.head.appendChild(styleEl);
533    }
534    styleEl.textContent += '\n' + cssBlocks;
535}
536
537function getHeaderMenuScrollHost(menuEl) {
538    if (!menuEl)
539        return null;
540
541    let hostEl = null;
542    let current = menuEl.parentElement;
543    while (current) {
544        if (current.classList && current.classList.contains(HEADER_MENU_SCROLL_HOST_CLASS))
545            hostEl = current;
546        current = current.parentElement;
547    }
548
549    if (hostEl)
550        return hostEl;
551
552    return menuEl.closest('#main-menu-container') || menuEl.parentElement;
553}
554
555function getHeaderMenuViewportWidth(hostEl) {
556    try {
557        const parentDivHeader = window.parent?.document?.getElementsByClassName('divHeader')?.[0];
558        if (parentDivHeader) {
559            const width = Math.round(parentDivHeader.getBoundingClientRect().width || parentDivHeader.clientWidth || 0);
560            if (width > 0)
561                return width;
562        }
563    } catch (error) {
564        // Same-origin access is best-effort only.
565    }
566
567    if (!hostEl)
568        return 0;
569
570    return Math.round(hostEl.getBoundingClientRect().width || hostEl.clientWidth || 0);
571}
572
573function pruneDuplicateHeaderMenuScrollNodes(hostEl) {
574    const uniqueByRole = function (selector) {
575        const nodes = Array.from(hostEl.querySelectorAll(selector));
576        if (nodes.length <= 1)
577            return nodes[0] || null;
578
579        nodes.slice(1).forEach(function (node) {
580            if (node && node.parentNode)
581                node.parentNode.removeChild(node);
582        });
583        return nodes[0];
584    };
585
586    const viewportEl = uniqueByRole('.header-menu-scroll__viewport');
587    const prevButton = uniqueByRole('.header-menu-scroll__button--prev');
588    const nextButton = uniqueByRole('.header-menu-scroll__button--next');
589
590    return {
591        viewportEl,
592        prevButton,
593        nextButton
594    };
595}
596
597function normalizeHeaderMenuScrollHostClasses(hostEl) {
598    Array.from(hostEl.querySelectorAll('.' + HEADER_MENU_SCROLL_HOST_CLASS)).forEach(function (node) {
599        if (node !== hostEl)
600            node.classList.remove(HEADER_MENU_SCROLL_HOST_CLASS);
601    });
602}
603
604// ── § 4  Core ────────────────────────────────────────────────────────────────
605
606function ensureHeaderMenuScrollMarkup() {
607    const menuEl = document.getElementById('main-menu') ||
608        (typeof mountEl !== 'undefined' && mountEl instanceof Element ? mountEl : null);
609    if (!shouldUseHeaderMenuScroll(menuEl))
610        return null;
611
612    const hostEl = getHeaderMenuScrollHost(menuEl);
613    if (!hostEl)
614        return null;
615
616    ensureHeaderMenuScrollStyle();
617
618    hostEl.classList.add(HEADER_MENU_SCROLL_HOST_CLASS);
619    normalizeHeaderMenuScrollHostClasses(hostEl);
620    const _hmscCTId = getScrollMenuCTId();
621    applyHeaderMenuScrollTheme(hostEl, _hmscCTId);
622    applyHeaderMenuScrollExtraCSS(hostEl, _hmscCTId);
623
624    const pruned = pruneDuplicateHeaderMenuScrollNodes(hostEl);
625
626    let viewportEl = pruned.viewportEl;
627    if (!viewportEl) {
628        viewportEl = document.createElement('div');
629        viewportEl.className = 'header-menu-scroll__viewport';
630        hostEl.insertBefore(viewportEl, menuEl);
631    }
632
633    if (menuEl.parentElement !== viewportEl)
634        viewportEl.appendChild(menuEl);
635    menuEl.classList.add('header-menu-scroll__list');
636
637    let prevButton = pruned.prevButton;
638    if (!prevButton) {
639        prevButton = document.createElement('button');
640        prevButton.type = 'button';
641        prevButton.className = 'header-menu-scroll__button header-menu-scroll__button--prev';
642        prevButton.setAttribute('aria-label', 'Scroll menu left');
643        prevButton.innerHTML = '&#8249;';
644        hostEl.appendChild(prevButton);
645    }
646
647    let nextButton = pruned.nextButton;
648    if (!nextButton) {
649        nextButton = document.createElement('button');
650        nextButton.type = 'button';
651        nextButton.className = 'header-menu-scroll__button header-menu-scroll__button--next';
652        nextButton.setAttribute('aria-label', 'Scroll menu right');
653        nextButton.innerHTML = '&#8250;';
654        hostEl.appendChild(nextButton);
655    }
656
657    return {
658        menuEl,
659        hostEl,
660        viewportEl,
661        prevButton,
662        nextButton
663    };
664}
665
666function initHeaderMenuScroll() {
667    const scrollParts = ensureHeaderMenuScrollMarkup();
668    if (!scrollParts)
669        return null;
670
671    const { menuEl, hostEl, viewportEl, prevButton, nextButton } = scrollParts;
672
673    if (menuEl.__headerMenuScroll &&
674        menuEl.__headerMenuScroll.hostEl === hostEl &&
675        menuEl.__headerMenuScroll.viewportEl === viewportEl) {
676        menuEl.__headerMenuScroll.update();
677        return menuEl.__headerMenuScroll;
678    }
679
680    if (menuEl.__headerMenuScroll && typeof menuEl.__headerMenuScroll.destroy === 'function')
681        menuEl.__headerMenuScroll.destroy();
682
683    const update = () => {
684        const viewportWidth = getHeaderMenuViewportWidth(hostEl);
685        if (viewportWidth > 0) {
686            hostEl.style.width = `${viewportWidth}px`;
687            hostEl.style.maxWidth = `${viewportWidth}px`;
688        }
689        const contentWidth = Math.max(
690            Math.round(viewportEl.scrollWidth || 0),
691            Math.round(menuEl.getBoundingClientRect().width || 0)
692        );
693        const isOverflow = viewportWidth > 0 && contentWidth > viewportWidth + 4;
694        const visibleWidth = Math.max(Math.round(viewportEl.clientWidth || viewportWidth || 0), 0);
695        const maxScrollLeft = Math.max(contentWidth - visibleWidth, 0);
696        const scrollLeft = Math.max(viewportEl.scrollLeft || 0, 0);
697
698        hostEl.classList.toggle(HEADER_MENU_SCROLL_ACTIVE_CLASS, isOverflow);
699
700        if (!isOverflow) {
701            if (scrollLeft > 0)
702                viewportEl.scrollLeft = 0;
703            prevButton.disabled = true;
704            nextButton.disabled = true;
705            return;
706        }
707
708        const safeScrollLeft = Math.min(scrollLeft, maxScrollLeft);
709        if (Math.abs(safeScrollLeft - scrollLeft) > 1)
710            viewportEl.scrollLeft = safeScrollLeft;
711
712        prevButton.disabled = safeScrollLeft <= 1;
713        nextButton.disabled = safeScrollLeft >= maxScrollLeft - 1;
714    };
715
716    const scrollByStep = (direction) => {
717        const viewportWidth = getHeaderMenuViewportWidth(hostEl);
718        const step = Math.max(160, Math.round(viewportWidth * 0.62));
719        viewportEl.scrollBy({
720            left: direction * step,
721            behavior: 'smooth'
722        });
723    };
724
725    const onScroll = () => update();
726    const onPrevClick = function (event) {
727        event.preventDefault();
728        scrollByStep(-1);
729    };
730    const onNextClick = function (event) {
731        event.preventDefault();
732        scrollByStep(1);
733    };
734
735    let resizeObserver = null;
736    let mutationObserver = null;
737
738    prevButton.addEventListener('click', onPrevClick);
739    nextButton.addEventListener('click', onNextClick);
740    viewportEl.addEventListener('scroll', onScroll, { passive: true });
741    window.addEventListener('resize', update);
742
743    if (window.ResizeObserver) {
744        resizeObserver = new ResizeObserver(update);
745        resizeObserver.observe(hostEl);
746        resizeObserver.observe(viewportEl);
747        resizeObserver.observe(menuEl);
748    }
749
750    if (window.MutationObserver) {
751        mutationObserver = new MutationObserver(update);
752        mutationObserver.observe(menuEl, { childList: true, subtree: true, characterData: true });
753    }
754
755    // Sub-menu fix: viewport has overflow-y:hidden which clips absolutely-positioned submenus.
756    // On hover, switch sub-menu to position:fixed so it breaks out of the scroll container.
757    const subMenuFixListeners = [];
758    function fixSubMenuPosition(li) {
759        const submenu = li.querySelector('.sub-menu');
760        if (!submenu) return;
761        const rect = li.getBoundingClientRect();
762        submenu.style.setProperty('position', 'fixed', 'important');
763        //submenu.style.setProperty('top',      rect.bottom + 'px', 'important');
764        submenu.style.setProperty('left', '0', 'important');
765        submenu.style.setProperty('right', '0', 'important');
766        submenu.style.setProperty('width', '100%', 'important');
767        submenu.style.setProperty('z-index', '99999', 'important');
768    }
769    function clearSubMenuPosition(li) {
770        const submenu = li.querySelector('.sub-menu');
771        if (!submenu) return;
772        ['position', 'top', 'left', 'right', 'width', 'z-index'].forEach(function (p) {
773            submenu.style.removeProperty(p);
774        });
775    }
776    Array.from(menuEl.querySelectorAll(':scope > li, :scope > td, :scope >
776 a')).forEach(function (li) {
777        const onEnter = function () { fixSubMenuPosition(li); };
778        const onLeave = function () { clearSubMenuPosition(li); };
779        li.addEventListener('mouseenter', onEnter);
780        li.addEventListener('mouseleave', onLeave);
781        subMenuFixListeners.push({ li, onEnter, onLeave });
782    });
783
784    // When the page scrolls vertically, clear position:fixed on all sub-menus so they
785    // don't stay frozen at the wrong viewport position while the header scrolls away.
786    const onWindowScroll = function () {
787        subMenuFixListeners.forEach(function (item) {
788            // DEBUG – uncomment to diagnose sub-menu freeze on vertical scroll:
789            // const submenu = item.li.querySelector('.sub-menu');
790            // const isHovered = item.li.matches && item.li.matches(':hover');
791            // const hasFixed = submenu && submenu.style.position === 'fixed';
792            // console.log('[onWindowScroll]', {
793            //     li: item.li.textContent.trim().slice(0, 30),
794            //     isHovered,
795            //     hasFixed,
796            //     submenuDisplay: submenu ? window.getComputedStyle(submenu).display : 'no-submenu',
797            //     submenuVisibility: submenu ? window.getComputedStyle(submenu).visibility : 'no-submenu',
798            // });
799            clearSubMenuPosition(item.li);
800        });
801    };
802    window.addEventListener('scroll', onWindowScroll, { passive: true });
803
804    menuEl.__headerMenuScroll = {
805        hostEl,
806        viewportEl,
807        update,
808        destroy() {
809            prevButton.removeEventListener('click', onPrevClick);
810            nextButton.removeEventListener('click', onNextClick);
811            viewportEl.removeEventListener('scroll', onScroll);
812            window.removeEventListener('resize', update);
813            window.removeEventListener('scroll', onWindowScroll);
814            if (resizeObserver)
815                resizeObserver.disconnect();
816            if (mutationObserver)
817                mutationObserver.disconnect();
818            subMenuFixListeners.forEach(function (item) {
819                item.li.removeEventListener('mouseenter', item.onEnter);
820                item.li.removeEventListener('mouseleave', item.onLeave);
821                clearSubMenuPosition(item.li);
822            });
823            subMenuFixListeners.length = 0;
824        }
825    };
826
827    update();
828    return menuEl.__headerMenuScroll;
829}
830
831function scheduleHeaderMenuScrollInit() {
832    if (headerMenuScrollInitTimer)
833        clearTimeout(headerMenuScrollInitTimer);
834
835    headerMenuScrollInitTimer = setTimeout(function () {
836        headerMenuScrollInitTimer = 0;
837        try {
838            initHeaderMenuScroll();
839        } catch (error) {
840            console.log(error);
841        }
842    }, 0);
843}
844
845// ╚═══════════════════════════ END: HEADER MENU SCROLL ═══════════════════════╝
846
847// use for TW account only and sportBookType = [1,2,3]
848function renameSPORTToSPORT12(sportItems, sportBookType) {
849    try {
850        sportItems.forEach(item => {
851            const $item = $(item);
852            const anchors = $item.is('a') ? $item : $item.find('a');
853            anchors.each(function () {
854                const $a = $(this);
855                switch (sportBookType) {
856                    case 1:
857                        $a.html(replaceSPORTLabelHtml($a.html(), 1));
858                        break;
859                    case 2:
860                        $a.html(replaceSPORTLabelHtml($a.html(), 2));
861                        const href = ($a.attr('href') || '').toLowerCase();
862                        let iconNew = getTopRightIconNEW(localStorage.getItem('CTId'));
863                        let top = isNaN(iconNew.top) ? iconNew.top : `${iconNew.top}px`
864                        const newGameTag = `<img title="new" src="_View/img/games/new.png?v=" class="blinking-image" style="filter:none; padding:0; z-index:999999; width: 25px!important; height: 13px; position: absolute; top: ${top}; right: ${iconNew.right}px;">`;
865                        $a.css('position', 'relative');
866                        $a.append(newGameTag);
867                        if ($a.attr('href') === '#' || href.indexOf('_view/rmodds1.aspx') > -1) {
868                            $a.attr({ href: 'LobbyGames.aspx?type=AFBSB', target: 'fraBody' });
869                        }
870                        break;
871                    case 3:
872                        $a.html(replaceSPORTLabelHtml($a.html(), 3));
873                        break;
874                }
875            });
876        });
877    }
878    catch (error) {
879        console.log(error)
880    }
881}
882
883function appendSPORT2(sportItems, CTId, currentTimestamp) {
884    addAllSportMenus(CTId, currentTimestamp, sportItems);
885}
886
887// Single-pass function that handles all sport combinations (1,2,3) based on sportBookTypeList
888function addAllSportMenus(CTId, currentTimestamp, sportItemsOverride) {
889    try {
890        const canS1 = canAccessSport(1);
891        const canS2 = canAccessSport(2);
892        const canS3 = canAccessSport(3);
893        if (!canS1 && !canS2 && !canS3) return;
894
895        const staticImageHost = localStorage.getItem('cdnImageHost') || 'https://img
895share.iuf-cfl.cloud';
896        const getImageType = (id) => id === 184 ? 'svg' : [137, 257].includes(id) ? 'webp' : 'png';
897        const imageType = getImageType(+CTId);
898
899        let iconNew = getTopRightIconNEW(CTId);
900        let top = isNaN(iconNew.top) ? iconNew.top : `${iconNew.top}px`;
901        const newGameTag = `<img title="new" src="_View/img/games/new.png?v=" class="blinking-image" style="filter:none; padding:0; z-index:999999; width: 25px!important; height: 13px; position: absolute; top: ${top}; right: ${iconNew.right}px;">`;
902
903        const sportItems = sportItemsOverride || (() => {
904            let items = getSPORTByHref();
905            if (items.length === 0) items = getSPORTByText();
906            return items;
907        })();
908        if (sportItems.length === 0) return;
909
910        sportItems.forEach(item => {
911            const $item = $(item);
912            const anchors = $item.is('a') ? $item : $item.find('a');
913
914            // Track insertion point so clones always land immediately after each other
915            let $lastInserted = $item;
916
917            // Determine what sport number to label the original item
918            const originalSportNum = canS1 ? 1 : (canS2 ? 2 : 3);
919
920            // Rename original and fix its href
921            anchors.each(function () {
922                const $a = $(this);
923                $a.html(replaceSPORTLabelHtml($a.html(), originalSportNum));
924                const href = ($a.attr('href') || '').toLowerCase();
925                if ($a.attr('href') === '#' || href.indexOf('_view/rmodds1.aspx') > -1) {
926                    if (canS1) $a.attr({ href: 'Body.aspx', target: 'fraBody' });
927                    else if (canS2) $a.attr({ href: 'LobbyGames.aspx?type=AFBSB', target: 'fraBody' });
928                    else $a.attr({ href: 'LobbyGames.aspx?type=OMSB', target: 'fraBody' });
929                }
930            });
931
932            // Update icon when original is repurposed as sport2 (canS1=false, canS2=true)
933            if (!canS1 && canS2) {
934                if (CTId === 126) {
935                    anchors.each(function () {
936                        const $a = $(this);
937                        const s = $a.attr('style') || '';
938                        $a.attr('style', s.replace('Images/theme/v1/img/sport.png', `${staticImageHost}/headergames/126/MenuIcon_LIGASB.png?v=${currentTimestamp}) left center no-repeat !important`));
939                    });
940                }
941                const imgOrig = $item.find('img');
942                if (imgOrig.length) imgOrig.attr('src', `${staticImageHost}/headergames/${CTId}/MenuIcon_LigaSB.${imageType}?v=${currentTimestamp}`);
943            }
944
945            // Clone for SPORT 2 — inserted immediately after original
946            if (canS2 && canS1) {
947                const clone2 = $item.clone();
948                const anchor2 = clone2.is('a') ? clone2 : clone2.find('a');
949                anchor2.attr({ href: 'LobbyGames.aspx?type=AFBSB', target: 'fraBody' });
950                anchor2.html(replaceSPORTLabelHtml(anchor2.html(), 2));
951                if (CTId === 126) {
952                    const s = anchor2.attr('style') || '';
953                    anchor2.attr('style', s.replace('Images/theme/v1/img/sport.png', `${staticImageHost}/headergames/126/MenuIcon_LIGASB.png?v=${currentTimestamp}) left center no-repeat !important`));
954                }
955                // Set menu icon src BEFORE appending newGameTag, else find('img') picks up the blinking img too
956                const img2 = clone2.find('img');
957                if (img2.length) img2.attr('src', `${staticImageHost}/headergames/${CTId}/MenuIcon_LigaSB.${imageType}?v=${currentTimestamp}`);
958                if (isNewGameLigaSB) {
959                    const s = anchor2.attr('style') || '';
960                    anchor2.attr('style', s + (s && !s.trim().endsWith(';') ? ';' : '') + ' position: relative!important;');
961                    anchor2.append(newGameTag);
962                }
963                $lastInserted.after(clone2);
964                $lastInserted = clone2;
965                console.log("Added SPORT 2 (LigaSB) menu item");
966            }
967
968            // Clone for SPORT 3 — inserted immediately after last inserted item
969            if (canS3) {
970                const clone3 = $item.clone();
971                const anchor3 = clone3.is('a') ? clone3 : clone3.find('a');
972                anchor3.attr({ href: 'LobbyGames.aspx?type=OMSB', target: 'fraBody' });
973                anchor3.html(replaceSPORTLabelHtml(anchor3.html(), 3));
974                // Set menu icon src BEFORE appending newGameTag, else find('img') picks up the blinking img too
975                const img3 = clone3.find('img');
976                if (img3.length) img3.attr('src', `${staticImageHost}/headergames/${CTId}/MenuIcon_OMSB.${imageType}?v=${currentTimestamp}`);
977                const s3 = anchor3.attr('style') || '';
978                anchor3.attr('style', s3 + (s3 && !s3.trim().endsWith(';') ? ';' : '') + ' position: relative!important;');
979                anchor3.append(newGameTag);
980                $lastInserted.after(clone3);
981                $lastInserted = clone3;
982                console.log("Added SPORT 3 (OMEGA) menu item");
983            }
984
985            // Remove original only when it was not repurposed (sport3-only: original is replaced by clone3)
986            if (!canS1 && !canS2) {
987                $item.remove();
988                console.log("Removed original SPORT item (sport1, sport2 not in list)");
989            }
990        });
991
992        addSPORTEvent();
993    }
994    catch (error) {
995        console.log(error);
996    }
997}
998
999function addSPORTEvent() {
1000    $('a').on('click touchstart', function (event) {
1001        try {
1002            /*var parent = window.parent.document;
1003            var href = $(this).attr('href');
1004            var target = $(this).attr('target');
1005            var divPanel = $(parent).find('.divPanel')[0];
1006            if (href !== '#' && target !== '_blank') {
1007                if (href.toLowerCase().indexOf('lobbygames.aspx') > -1) {
1008                    divPanel.style.cssText = "display:none";
1009                    let width = isMobileDevice() ? 'width: 100% !important;' : 'width: 1024px !important;';
1010                    console.log(width);
1011                    $(parent).find('.divMain')[0].style.cssText = width;
1012                } else {
1013                    divPanel.style.cssText = "display:inline";
1014                    $(parent).find('.divMain').css('width', 'calc(100% - 205px)');
1015
1016                    //localStorage.setItem('ligaSbHost', '');
1017                    //$('#accTpLst', window.parent.frames["fraHeader"].document).attr('onchange', 'top.location.href = this.form.accTpLst.options[this.form.accTpLst.selectedIndex].value; return false;');
1018                    //let fraPanel = window.parent.document.getElementsByName("fraPanel")[0];
1019                    //if (fraPanel && !fraPanel.src.includes("Panel.aspx")) {
1020                    //    fraPanel.src = "_Bet/Panel.aspx";
1021                    //}
1022                }
1023            }*/
1024            var href = $(this).attr('href');
1025            var target = $(this).attr('target');
1026            var nonSportLinks = getNonSPORTLinks();
1027            //event.preventDefault();
1028            if (href.toLowerCase().indexOf('lobbygames.aspx') > -1 && !isSportOnly(1))
1029                replaceTargetFraBody(nonSportLinks, 'fraBody');
1030            else if (href.toLowerCase().indexOf('body.aspx') > -1 && !isSportOnly(1))
1031                replaceTargetFraBody(nonSportLinks, 'fraMain');
1032            if (href !== undefined && href !== '#' && target !== '_blank' && target !== '_top' && target !== '_parent') {
1033                if (href.toLowerCase().indexOf('lobbygames.aspx') > -1 || href.toLowerCase().indexOf('body.aspx') > -1) {
1034                    // Track selected sport on direct sport navigation
1035                    if (href.toLowerCase().indexOf('type=omsb') > -1) setSelectedSport(3);
1036                    else if (href.toLowerCase().indexOf('type=afbsb') > -1) setSelectedSport(2);
1037                    else if (href.toLowerCase().indexOf('body.aspx') > -1) setSelectedSport(1);
1038                    // open normally
1039                }
1040                else {
1041                    event.preventDefault();
1042                    if (target == 'fraMain')
1043                        window.open(href, target);
1044                    else if (target == 'fraBody') {
1045                        if (isFraMainLoaded()) {
1046                            if (href.toLowerCase().indexOf('body.aspx') === -1)
1047                                window.open(href, 'fraMain');
1048                            else
1049                                window.open('_View/RMOdds1.aspx', 'fraMain');
1050                        }
1051                        else {
1052                            if (href.toLowerCase().indexOf('body.aspx') > -1)
1053                                href = '_View/RMOdds1.aspx';
1054                            else if (href.toLowerCase().indexOf('//') > -1)
1055                                href = encodeURIComponent(href)
1056
1057                            window.open('Body.aspx?href=' + href + getLpnlParam(), target);
1058                        }
1059                    }
1060                }
1061            }
1062        } catch (e) {
1063            console.log(e);
1064        }
1065    });
1066}
1067
1068function isFraMainLoaded() {
1069    try {
1070        // Access the sibling iframe by its ID or index
1071        const siblingFrame = parent.frames['fraBody'].document.getElementsByName("fraMain");
1072
1073        // Attempt to access its document
1074        // This will throw a SecurityError if it's cross-origin
1075        return !!siblingFrame[0].contentDocument;
1076    } catch (e) {
1077        // A SecurityError means it's cross-origin
1078        return false;
1079    }
1080
1081    //try {
1082    //    return parent.frames['fraBody'].frames['fraMain'] !== undefined
1083    //}
1084    //catch (e) {
1085    //    return false;
1086    //}
1087}
1088// only set target fraBody for load ligasb as default
1089function replaceTargetFraBody(anchorNonSPORTList, targetValue = 'fraBody') {
1090    if (!anchorNonSPORTList)
1091        anchorNonSPORTList = getNonSPORTLinks()
1092    anchorNonSPORTList.forEach(function (el) {
1093        const $el = $(el);
1094        const $anchor = $el.is('a') ? $el : $el.find('a').first();
1095        if ($anchor.length && $anchor.is('[target]') && $anchor.attr('target') !== '_blank' && $anchor.attr('target') !== '_top' && $anchor.attr('target') !== '_parent') {
1096            //console.log($anchor.html() + '-' + $anchor.attr('target'));
1097            $anchor.attr('target', targetValue);
1098        }
1099    });
1100}
1101
1102function addSPORT2Menu(CTId, currentTimestamp) {
1103    addAllSportMenus(CTId, currentTimestamp);
1104}
1105
1106function applyAddMHClones() {
1107    const addMH = +(getQueryParam('AddMH') || 0);
1108    if (!(addMH > 0)) return;
1109    const menuEl = (typeof mountEl !== 'undefined' && mountEl instanceof Element ? mountEl : null) ||
1110        document.getElementById('main-menu');
1111    if (!menuEl) return;
1112    const items = Array.from(menuEl.children).filter(function (child) {
1113        const tagName = child.tagName.toLowerCase();
1114        return (tagName === 'li' || tagName === 'a') && !(child.classList && child.classList.contains('icon'));
1115    });
1116    if (!items.length) return;
1117    const lastItem = items[items.length - 1];
1118    for (let i = 0; i < addMH; i++) {
1119        menuEl.appendChild(lastItem.cloneNode(true));
1120    }
1121}
1122
1123function removeTANGKAS() {
1124    var tags = $(mountEl).find('a').filter(function () {
1125        return $(this).attr('href') && $(this).attr('href').toLowerCase().includes('tangkas');
1126    });
1127
1128    if (tags.length > 0) {
1129        tags.each(function () {
1130            if ($(this).closest('li').length > 0) {
1131                $(this).closest('li').remove();
1132                console.log("Tangkas menu item removed from <li>");
1133            } else {
1134                $(this).remove();
1135                console.log("Tangkas menu item removed from <a>");
1136            }
1137        });
1138    } else {
1139        console.log("No <a> tags found with the text tangkas");
1140    }
1141}
1142
1143function replaceSLOTUrl() {
1144    var tags = $(mountEl).find('a').filter(function () {
1145        return $(this).attr('href') && $(this).attr('href').toLowerCase().includes('gt=slot');
1146    });
1147
1148    if (tags.length > 0) {
1149        tags.each(function () {
1150            $(this).attr('href', 'SlotTW.aspx')
1151        });
1152    }
1153    else
1154        console.log("No <a> tags found with the text slot");
1155}
1156
1157function getTopRightIconNEW(CTId, isGuest) {
1158    let isSWAccount = localStorage.getItem('isSWAccount') === 'true';
1159    let top = 'auto', right = 0
1160    switch (+CTId) {
1161        case 47: top = isHeaderPage() ? -5 : 'auto'; break;
1162        case 57:
1163        case 63:
1164        case 67:
1165        case 69:
1166        case 75:
1167        case 82:
1168        case 83:
1169        case 87:
1170        case 164:
1171        case 167:
1172        case 168:
1173        case 172:
1174        case 179:
1175        case 225:
1176        case 272:
1177        case 317:
1178            //top = -9; break;
1179            top = 0; break;
1180
1181        case 82:
1182        case 86:
1183        case 90:
1184        case 95:
1185        case 96:
1186        case 99:
1187            top = 0; break;
1188
1189        case 101:
1190            top = isHeaderPage() ? -9 : 'auto'; break;
1191
1192        case 103:
1193        case 104:
1194        case 115:
1195        case 124:
1196        case 125:
1197            top = 0; break;
1198
1199        case 137:
1200            top = 0; right = -14; break;
1201
1202        case 109:
1203        case 142:
1204        case 155:
1205            top = isHeaderPage() ? 0 : 'auto'; break;
1206
1207        case 149:
1208        case 150:
1209        case 152:
1210        case 151:
1211        case 153:
1212        case 181:
1213        case 182:
1214        case 183:
1215        case 184:
1216        case 193:
1217        case 204:
1218        case 213:
1219        case 217:
1220        case 221:
1221        case 243:
1222        case 244:
1223        case 256:
1224        case 263:
1225        case 264:
1226        case 268:
1227        case 273:
1228        case 289:
1229        case 296:
1230        case 303:
1231        case 322:
1232            top = 0; break;
1233
1234        case 235:
1235            top = isHeaderPage() ? (isGuest ? 4 : (isSWAccount ? -40 : 0)) : 11;
1236            right = isHeaderPage() ? 21 : 23;
1237            break;
1238        case 277: top = isHeaderPage() ? -9 : 0; break;
1239
1240        case 279:
1241            if (!isHeaderPage()) {
1242                right = 29;
1243            }
1244            else {
1245                if (isGuest) { right = 35; top = 0; }
1246                else { right = 9; top = -11; }
1247            }
1248            break;
1249        case 89: top = 'auto'; right = 1200; break;
1250        case 298: right = 29; top = isGuest ? 0 : -12; break;
1251        case 299: right = -17; break;
1252        case 301: top = 0; right = -20; break;
1253        case 306: top = 0; right = -5; break;
1254    }
1255    let newIcon = { top, right }
1256    return newIcon
1257}
1258
1259function setLanguage(langParam, callback) {
1260    let url = 'Handlers/LanguageHandler.ashx?lang=' + langParam;
1261    fetch(url, {
1262        method: 'GET',
1263        headers: {
1264            'Accept': 'text/plain'
1265        }
1266    })
1267        .then(response => {
1268            if (!response.ok) throw new Error("HTTP error " + response.status);
1269            return response.text();
1270        })
1271        .then(data => {
1272            callback(data);
1273        })
1274        .catch(err => {
1275            alert("Fetch error:", err);
1276        });
1277}
1278
1279let fraHeaderDocument = null;
1280try {
1281    if (window.parent && window.parent.frames["fraHeader"])
1282        fraHeaderDocument = window.parent.frames["fraHeader"].document;
1283}
1284catch (e) {
1285    fraHeaderDocument = document;
1286}
1287// Vue App and Components part
1288const { createApp, ref, onMounted, computed, onBeforeMount, provide, inject } = Vue;
1289const SpanArrow = {
1290    template: '<span class="arrow-down-submenu">▼</span>',
1291};
1292const DivArrow = {
1293    template: '<div class="arrow-down"></div>',
1294};
1295const TextArrow = {
1296    template: '▼',
1297};
1298const DownArrow = {
1299    props: {
1300        type: String,
1301        hasSubGame: Boolean,
1302        gameType: String,
1303        isGuest: Boolean,
1304        hideOnMobileResponsive: {
1305            type: Boolean,
1306            default: false
1307        }
1308    },
1309    components: {
1310        SpanArrow,
1311        DivArrow,
1312        TextArrow,
1313    },
1314    computed: {
1315        getArrowComponent() {
1316            switch (this.type) {
1317                case 'span':
1318                    return SpanArrow;
1319                case 'div':
1320                    return DivArrow;
1321                case 'text':
1322                    return TextArrow;
1323            }
1324        },
1325        isVisible() {
1326            if (isMobileDevice() && this.hideOnMobileResponsive)
1327                return false;
1328            return this.isGuest ? (this.hasSubGame ? true : false) : (this.gameType.toLowerCase() === 'afbsb' ? (this.hasSubGame ? true : false) : false);
1329        }
1330    },
1331    template: `
1332    <component :is="getArrowComponent" v-if="isVisible"></component>
1333  `,
1334};
1335const ImageItem = {
1336    props: {
1337        className: {
1338            type: String,
1339            default: '',
1340        },
1341        cacheVersion: {
1342            type: Number,
1343            default: 0,
1344        },
1345        imageData: {
1346            type: Object,
1347            required: true,
1348        },
1349        isSubmenu: {
1350            type: Boolean,
1351            default: false,
1352        },
1353        width: {
1354            type: String,
1355            default: 'auto',
1356        },
1357        height: {
1358            type: String,
1359            default: 'auto',
1360        },
1361    },
1362    data() {
1363        return {
1364            currentTimestamp: null,
1365            errorHandled: false,
1366            showErrorImage: false,
1367        };
1368    },
1369    created() {
1370        this.currentTimestamp = this.getCurrentTimeStamp;
1371    },
1372    setup() {
1373        const ligaSbSportSubmenuName = inject('ligaSbSportSubmenuName');
1374        return { ligaSbSportSubmenuName };
1375    },
1376    methods: {
1377        handleError() {
1378            if (!this.errorHandled) {
1379                let newTimestamp = this.getRefreshTimestampMS;
1380                //let newTimestamp = this.getRefreshTimestampH;
1381                const refreshTimestamp = +(localStorage.getItem('HMNCacheV2') || 0);
1382                if (refreshTimestamp >= this.currentTimestamp)
1383                    //if (refreshTimestamp.toString().indexOf("17") === -1 && refreshTimestamp >= this.currentTimestamp)
1384                    newTimestamp = refreshTimestamp;
1385
1386                localStorage.setItem('HMNCacheV2', newTimestamp);
1387                localStorage.removeItem('HMNCacheV');
1388                this.errorHandled = true;
1389                this.showErrorImage = true;
1390
1391                const delays = [1, 2, 3, 5, 8, 13, 21, 34, 55];
1392                const delay = delays[Math.floor(Math.random() * delays.length)];
1393
1394                setTimeout(() => {
1395                    this.currentTimestamp = newTimestamp;
1396                    this.showErrorImage = false;
1397                }, delay * 1000);
1398            }
1399        },
1400    },
1401    computed: {
1402        src() {
1403            if (this.showErrorImage)
1404                return "Images/spinner.gif";
1405            let staticImageHost = localStorage.getItem('cdnImageHost') || 'https://imgshare.iuf-cfl.cloud';
1406            let gameName = (+this.imageData.CTId === 13 && isLigaSbName(this.imageData.GameName, this.ligaSbSportSubmenuName)) ? 'LIGA 365' : this.imageData.GameName;
1407
1408            const basePath = this.isSubmenu
1409                ? `/headergames/SubMenuIcon_${this.imageData.HGameId}_${gameName}.png`
1410                : `/headergames/${this.imageData.CTId}/MenuIcon_${this.imageData.GameType}.${this.imageData.ImageType}`;
1411            return `${staticImageHost}${basePath}?v=${this.currentTimestamp}`;
1412        },
1413        style() {
1414            if (this.showErrorImage)
1415                return "width:32px!important; height:32px!important;";
1416            return `width:${this.width}; height:${this.height};`;
1417        },
1418        getCurrentTimeStamp() {
1419            const dbTimestamp = +extractTimestampFromString(this.imageData.HeaderLastUpdatedTime);
1420            const serverTimestamp = +this.cacheVersion;
1421            let latestTimestamp = Math.max(dbTimestamp, serverTimestamp);
1422
1423            const refreshTimestamp = +(localStorage.getItem('HMNCacheV2') || 0);
1424            latestTimestamp = Math.max(latestTimestamp, refreshTimestamp);
1425            return latestTimestamp;
1426        },
1427        getRefreshTimestampH() {
1428            const date = new Date();
1429            const year = date.getFullYear();
1430            const month = (date.getMonth() + 1).toString().padStart(2, '0');
1431            const day = date.getDate().toString().padStart(2, '0');
1432            const hours = date.getHours().toString().padStart(2, '0');
1433            //const minuteGroup = Math.floor(date.getMinutes() / 30).toString();
1434            return +`${year}${month}${day}${hours}`;
1435        },
1436        getRefreshTimestampMS() {
1437            return this.getCurrentTimeStamp + 1;
1438        }
1439    },
1440    template: `
1441        <img 
1442            :style="style" 
1443            :class="className" 
1444            :src="src" 
1445            :alt="imageData.GameMenuDisplayName" 
1446            @error="handleError" 
1447        />
1448    `,
1449};
1450
1451const MenuItem = {
1452    props: {
1453        name: String,
1454        className: String,
1455        onclickHandler: Function,
1456        isGuest: Boolean,
1457        hrefs: Object
1458    },
1459    setup() {
1460        const isLigaSbOnly = inject('isLigaSbOnly');
1461        const hasTangkas = inject('hasTangkas');
1462        const cacheVersion = inject('cacheVersion');
1463        const CTId = inject('CTId');
1464        const isGuest = inject('isGuest');
1465        function onclickFunc(event) {
1466            try {
1467                var parent = window.parent.document;
1468                var href = this.href;
1469                var target = this.target
1470                // Track selected sport when user clicks a sport menu item
1471                const hrefLower = (href || '').toLowerCase();
1472                if (hrefLower.indexOf('type=omsb') > -1) setSelectedSport(3);
1473                else if (hrefLower.indexOf('type=afbsb') > -1) setSelectedSport(2);
1474                else if (hrefLower.indexOf('body.aspx') > -1) setSelectedSport(1);
1475                let CTId = +localStorage.getItem('CTId')
1476                // CTId list of WLs use Default page as Main page
1477                let DMLayoutCTIds = [60]
1478                //console.log(href)
1479                if (href !== undefined && href !== '#' && target !== '_blank') {
1480                    if (href.toLowerCase().indexOf('lobbygames.aspx?type=afbsb') > -1 || href.toLowerCase().indexOf('lobbygames.aspx?type=omsb') > -1 || href.toLowerCase().indexOf('body.aspx') > -1) {
1481                        //window.open(href, target);
1482                    } else {
1483                        if (this.isLigaSbOnly) {
1484                            // only SPORT 2
1485                        }
1486                        else {
1487                            event.preventDefault();
1488                            if (target == 'fraMain')
1489                                window.open(href, target);
1490                            else if (target == 'fraBody') {
1491                                if (isFraMainLoaded()) {
1492                                    if (href.toLowerCase().indexOf('body.aspx') === -1) {
1493                                        window.open(href, 'fraMain');
1494                                        //window.parent.frames['fraBody'].postMessage({ ACT: 'LoadRightGame', value: null }, '*');
1495                                    }
1496                                    else
1497                                        window.open('_View/RMOdds1.aspx', 'fraMain');
1498                                }
1499                                else {
1500                                    if (href.toLowerCase().indexOf('body.aspx') > -1)
1501                                        href = '_View/RMOdds1.aspx';
1502                                    else if (href.toLowerCase().indexOf('//') > -1)
1503                                        href = encodeURIComponent(href)
1504                                    window.open('Body.aspx?href=' + href + getLpnlParam(), target);
1505                                }
1506                            }
1507                        }
1508                    }
1509                }
1510
1511                if (this.isGuest && !isHeaderPage()) {
1512                    // Guest on login page (all in Defaults/ subdirectory).
1513                    // Set sport in session via MainGuestHandler, then navigate to app root (clean URL).
1514                    // App root is computed by stripping '/Defaults/filename' from current URL —
1515                    // works for any virtual path (localhost, subdir, etc.).
1516                    event.preventDefault();
1517                    let sportPage = 's1';
1518                    switch (this.name.toLowerCase()) {
1519                        case 'ligasb':
1520                            sportPage = 's2'; break;
1521                        case 'omsb':
1522                            sportPage = 's3'; break;
1523                        default:
1524                            sportPage = 's1'; break
1525                    }
1526                    $.ajax({
1527                        url: 'Handlers/MainGuestHandler.ashx?p=m&s=' + sportPage,
1528                        method: 'GET',
1529                        success: function (response) {
1530                            console.log("Server response:", response);
1531                            //location.reload();
1532                            //fixed for firefox load default page after transfer to Main page (live site)
1533                            location.href = location.href.replace(/#$/, '');
1534                        },
1535                        error: function (xhr) {
1536                            //console.error("AJAX error:", xhr);
1537                        }
1538                    });
1539                }
1540                // only for BLPLNGI, BLMAS8, IDWS
1541                if ([60, 55, 81].includes(CTId) && this.isGuest && isHeaderPage() && this.name.toLowerCase() === 'afbsb') {
1542                    event.preventDefault();
1543                    if ([60].includes(CTId)) // only for BLPLNGI
1544                        window.parent.document.getElementsByClassName('home')[0].style.width = '1024px'
1545                    if (isFraMainLoaded())
1546                        window.open('_View/RMOdds1.aspx', 'fraMain');
1547                    else
1548                        window.open('Body.aspx?href=_View/RMOdds1.aspx', 'fraBody');
1549                }
1550                else if ([60].includes(CTId) && this.isGuest && isHeaderPage() && (this.name.toLowerCase() === 'ligasb' || this.name.toLowerCase() === 'omsb'))
1551                    window.parent.document.getElementsByClassName('home')[0].style.width = '1024px'
1552            }
1553            catch (e) {
1554                console.log(e)
1555            }
1556        }
1557        return { isLigaSbOnly, hasTangkas, cacheVersion, CTId, isGuest, onclickFunc };
1558    },
1559    computed: {
1560        formattedClassName() {
1561            let prefix = addPrefix(this.name);
1562            return `${this.className ? this.className : ''
1563                } ${prefix}${this.name.toLowerCase()}-game`;
1564        },
1565        formattedName() {
1566            return this.name.toLowerCase();
1567        },
1568        refQueryStr() {
1569            return getQueryParam('ref') ? '?ref=' + getQueryParam('ref') : '';
1570        },
1571        href() {
1572            if (this.isGuest) {
1573                if (this.name.toLowerCase() === 'afbsb') {
1574                    if (isHeaderPage())
1575                        return this.hasSubGameSport ? '#' : (this.isLigaSbOnly ? 'LobbyGames.aspx?type=AFBSB' : 'Body.aspx');
1576                    return this.hasSubGameSport ? '#' : this.refQueryStr
1577                }
1578                else if (this.name.toLowerCase() === 'ligasb') {
1579                    return isHeaderPage() ? 'LobbyGames.aspx?type=AFBSB' : this.refQueryStr
1580                }
1581                else if (this.name.toLowerCase() === 'omsb') {
1582                    return isHeaderPage() ? 'LobbyGames.aspx?type=OMSB' : this.refQueryStr
1583                }
1584                return '#';
1585            }
1586            else
1587                switch (this.name.toLowerCase()) {
1588                    case 'afbsb':
1589                        if (isSportOnly(2) && !this.hasSubGameSport)
1590                            return 'LobbyGames.aspx?type=AFBSB';
1591                        return this.hasSubGameSport ? '#' : (this.isLigaSbOnly ? 'LobbyGames.aspx?type=AFBSB' : 'Body.aspx');
1592                    case 'ligasb':
1593                        return 'LobbyGames.aspx?type=AFBSB';
1594                    case 'omsb':
1595                        return 'LobbyGames.aspx?type=OMSB';
1596                    case 'poker':
1597                    // tangkas will be moved to param of slot url
1598                    case 'tangkas':
1599                        return this.hrefs[this.name.toLowerCase()];
1600                    case 'slot':
1601                        return 'LobbyGames.aspx?type=' + this.name + (this.hasTangkas ? '&urltks=' + encodeURIComponent(this.hrefs['tangkas']) : '');
1602                    default:
1603                        return 'LobbyGames.aspx?type=' + this.name;
1604                }
1605        },
1606        target() {
1607            return this.href === '#' ? '' : (isHeaderPage() ? 'fraBody' : '');
1608        },
1609        hasSubGameSport() {
1610            return JSON.parse(localStorage.getItem('menuMap_user') || localStorage.getItem('menuMap') || '{}')?.['AFBSB']?.[0]?.HasSubGame ?? false;
1611        },
1612        isNewGame() {
1613            return JSON.parse(localStorage.getItem('menuMap_user') || localStorage.getItem('menuMap') || '{}')?.[this.name.toUpperCase()]?.[0]?.IsNewGame ?? false;
1614        },
1615        styleIconNEW() {
1616            let iconNew = getTopRightIconNEW(this.CTId, this.isGuest);
1617            let top = isNaN(iconNew.top) ? iconNew.top : `${iconNew.top}px`
1618            let strStyle = `filter:none; padding:0; z-index:999999; width: 25px!important; height: 13px;position: absolute; top: ${top}; right: ${iconNew.right}px;`
1619            return strStyle;
1620        },
1621        styleMenuItem() {
1622            return this.isNewGame ? 'position: relative!important' : ''
1623        }
1624    },
1625    template: `<a @click="onclickFunc($event)" :href="href" v-bind:target="target || null" :class="formattedClassName" :name="formattedName" :style="styleMenuItem" vue-click="true">
1626                  <img title="new" v-if="isNewGame"
1627                     :src="'_View/img/games/new.png?v=' + cacheVersion"
1628                     class="blinking-image"
1629                     :style="styleIconNEW">
1630                <slot></slot>
1631            </a>`
1632};
1633const SubMenuContainer = {
1634    props: ['name', 'className'],
1635    computed: {
1636        id() {
1637            return 'sub-menu-' + this.name.toLowerCase();
1638        },
1639        formattedClass() {
1640            return `sub-menu ${this.className || ''}`;
1641        },
1642    },
1643    template: `
1644      <div :id="id" :class="formattedClass">
1645          <div class="main_width">
1646          <ul class="gameNav">
1647              <slot></slot>
1648          </ul>
1649          </div>
1650      </div>
1651  `,
1652};
1653const SubMenuItem = {
1654    props: ['name', 'type', 'isGuest'],
1655    setup() {
1656        const ligaSbSportSubmenuName = inject('ligaSbSportSubmenuName');
1657        return { ligaSbSportSubmenuName };
1658    },
1659    computed: {
1660        href() {
1661            if (this.isGuest) {
1662                if (this.type.toLowerCase() === 'afbsb') {
1663                    if (isHeaderPage()) {
1664                        //switch (this.name.toLowerCase()) {
1665                        //    case 'liga sb':
1666                        //    case 'liga 365':
1667                        //        return 'LobbyGames.aspx?type=' + this.type;
1668                        //    //case 'sport': return '_View/RMOdds1.aspx';
1669                        //    default: return '_View/RMOdds1.aspx';
1670                        //}
1671                        if (isLigaSbName(this.name, this.ligaSbSportSubmenuName))
1672                            return 'LobbyGames.aspx?type=' + this.type;
1673                        return '_View/RMOdds1.aspx';
1674                    }
1675                    else { // default page
1676                        let hrefMain = getQueryParam('ref') ? 'Main.aspx?ref=' + getQueryParam('ref') : 'Main.aspx'
1677                        //switch (this.name.toLowerCase()) {
1678                        //    case 'liga sb':
1679                        //    case 'liga 365':
1680                        //        return hrefMain + (hrefMain.indexOf('?') > -1 ? '&' : '?') + 'launch=ligasb';
1681                        //    //case 'sport': return '_View/RMOdds1.aspx';
1682                        //    default: return hrefMain;
1683                        //}
1684                        return hrefMain;
1685                    }
1686                }
1687                return '#';
1688            }
1689            else {
1690                //switch (this.name.toLowerCase()) {
1691                //    case 'liga sb':
1692                //    case 'liga 365':
1693                //        return 'LobbyGames.aspx?type=' + this.type;
1694                //    case 'sport':
1695                //        return '_View/RMOdds1.aspx';
1696                //    default:
1697                //        return '#';
1698                //}
1699                if (isLigaSbName(this.name, this.ligaSbSportSubmenuName))
1700                    return 'LobbyGames.aspx?type=' + this.type;
1701                else if (this.name.toLowerCase().indexOf('sport') > -1)
1702                    return '_View/RMOdds1.aspx';
1703                return '#';
1704            }
1705        },
1706        target() {
1707            return this.href === '#' ? '' : (isHeaderPage() ? 'fraMain' : '_parent');
1708        },
1709    },
1710    template: `
1711    <li>
1712        <a :type="type" style="background:none!important; position:relative!important" :href="href" v-bind:target="target || null">
1713            <slot></slot>
1714            {{name}}
1715        </a>
1716    </li>
1717  `,
1718};
1719
1720const SubMenu = {
1721    props: {
1722        className: String,
1723        submenuData: Object,
1724        isGuest: Boolean,
1725        cacheVersion: Number,
1726        hideOnMobileResponsive: {
1727            type: Boolean,
1728            default: false
1729        }
1730    },
1731    computed: {
1732        isSubMenu() {
1733            return true;
1734        },
1735        isVisible() {
1736            if (isMobileDevice() && this.hideOnMobileResponsive)
1737                return false;
1738            return this.isGuest ? (this.submenuData[0].HasSubGame ? true : false) : (this.gameType === 'afbsb' ? (this.submenuData[0].HasSubGame ? true : false) : false);
1739        },
1740        gameType() {
1741            return this.submenuData[0].GameType.toLowerCase();
1742        }
1743    },
1744    template: `
1745        <submenu-container v-if="isVisible" :class-name="className" :name="submenuData[0].GameType">
1746            <submenu-item v-for="(record, index) in submenuData"
1747                          :type="record.GameType" 
1748                          :is-guest="isGuest"
1749                          :name="record.GameName">
1750                <image-item :image-data="record" :key="index" :is-submenu="isSubMenu" :cache-version="cacheVersion" />
1751                <img title="hot" v-if="record.IsHotGame" 
1752                     :src="'css/submenu/hot-icon.gif?v=' + cacheVersion" 
1753                     style="width: 36px!important;height: 36px;position: absolute;top: -12px;left: 12px;">
1754                <img class="gacor" title="hot" v-if="record.IsGacorGame"
1755                     :src="'_View/img/games/gacor.ico?v=' + cacheVersion"
1756                     style="width: 36px!important;height: 36px;position: absolute;top: -12px;left: 12px;">
1757                <img title="new" v-if="record.IsNewGame" 
1758                     :src="'_View/img/games/new.png?v=' + cacheVersion" 
1759                     class="blinking-image" 
1760                     style="width: 32px!important;height: 15px;position: absolute;top: 0px;left: 10px;">
1761            </submenu-item>
1762        </submenu-container>
1763    `,
1764};
1765
1766function createMenu() {
1767    const refText = 'Referral';
1768    return createApp({
1769        methods: {
1770            getGameMenuDisplayNameByLang(menuData, lang) {
1771                let name = menuData.GameMenuDisplayName;
1772                if (!lang)
1773                    lang = getQueryParam('lang')
1774                switch (lang) {
1775                    case 'ZH-CN': name = this.getValidName(menuData, 'GameMenuDisplayNameC'); break;
1776                    case 'TH-TH': name = this.getValidName(menuData, 'GameMenuDisplayNameT'); break;
1777                    case 'EN-IE': name = this.getValidName(menuData, 'GameMenuDisplayNameV'); break;
1778                    case 'EN-TT': name = this.getValidName(menuData, 'GameMenuDisplayNameK'); break;
1779                    case 'EN-AU': name = this.getValidName(menuData, 'GameMenuDisplayNameI'); break;
1780                }
1781                return name;
1782            },
1783            getValidName(menuData, key) {
1784                if (menuData[key] && menuData[key] !== '')
1785                    return menuData[key]
1786                return menuData.GameMenuDisplayName;
1787            },
1788            i(menuData) {
1789                return this.getGameMenuDisplayNameByLang(menuData);
1790            },
1791            fetching_menu(menuData) {
1792                return this.getGameMenuDisplayNameByLang(menuData);
1793            },
1794        },
1795        setup() {
1796            let data = ref({});
1797            let dataFetched = ref(false);
1798            let appId = ref('');
1799            let cacheVersion = ref(0);
1800            let isGuest = ref(true);
1801            let hideSubmenuMobileResponsive = ref(false);
1802            let hiddenReferralMenu = ref(false)
1803            let menuUrls = ref({});
1804            let isLigaSbOnly = ref(false);
1805            let ligaSbSportSubmenuName = ref('');
1806            let hasTangkas = ref(false);
1807            let CTId = ref(0);
1808            provide('isLigaSbOnly', isLigaSbOnly);
1809            provide('ligaSbSportSubmenuName', ligaSbSportSubmenuName);
1810            provide('hasTangkas', hasTangkas);
1811            provide('cacheVersion', cacheVersion);
1812            provide('CTId', CTId);
1813            provide('isGuest', isGuest);
1814
1815            const fetchMenuData = (callback) => {
1816                const queryString = window.location.search.substring(1);
1817                const apiUrl = 'public/GameGen.ashx?cmd=GetHeaderMenuGames&' + queryString;
1818                $.ajax({
1819                    url: apiUrl,
1820                    method: 'GET',
1821                    contentType: 'application/json',
1822                    dataType: 'json',
1823                    success: function (response) {
1824                        const sportBookType = +(response.sportBookType || 0);
1825                        const sportBookTypeList = response.sportBookTypeList || '0';
1826                        const _typeListParsed = sportBookTypeList.split(',').map(s => s.trim()).filter(Boolean);
1827                        const sportBookDef = (sportBookTypeList !== '0' && _typeListParsed.length > 0)
1828                            ? (+_typeListParsed[0] || 2)
1829                            : +(response.sportBookDef || 2);
1830                        let menuData = response.menus
1831                        if (response && response.success) {
1832                            menuData = filterMenuDataBySportBookType(menuData, sportBookTypeList);
1833                            if (!isGuest.value) {
1834                                if (response.hasReferral && !hiddenReferralMenu.value) {
1835                                    const lastRecord = menuData.at(-1)
1836                                    menuData.push({
1837                                        ...lastRecord,
1838                                        Id: lastRecord.Id + 1,
1839                                        GameType: refText,
1840                                        GameMenuDisplayName: refText,
1841                                        GameMenuDisplayNameC: refText,
1842                                        GameMenuDisplayNameT: refText,
1843                                        GameMenuDisplayNameV: refText,
1844                                        GameMenuDisplayNameK: refText,
1845                                        GameMenuDisplayNameC: refText,
1846                                        GameMenuDisplayNameI: refText,
1847                                    })
1848                                }
1849                            }
1850                            let menuMap = groupBy(menuData, 'GameType');
1851                            localStorage.setItem('sportBookType', sportBookType);
1852                            localStorage.setItem('sportBookTypeList', sportBookTypeList);
1853                            localStorage.setItem(isGuest.value ? 'menuMap' : 'menuMap_user', JSON.stringify(menuMap));
1854                            if (isGuest.value || !isHeaderPage()) {
1855                                delete menuMap['ALLGAMES'];
1856                                delete menuMap[refText];
1857                            }
1858                            console.log(menuMap)
1859
1860                            syncMenuMap(data.value, menuMap);
1861                            scheduleHeaderMenuScrollInit();
1862                            dataFetched.value = true;
1863                            isLigaSbOnly.value = response.isLigaSbOnly
1864                            ligaSbSportSubmenuName.value = menuMap['AFBSB']?.find(game => game.IsLigaSB)?.GameName ?? "";
1865                            ligaSbSportSubmenuNameTW = ligaSbSportSubmenuName.value;
1866                            hasTangkas.value = menuMap['SLOT']?.some(game => game.GameName.toUpperCase() === 'TANGKAS') ?? false;
1867                            CTId.value = +response.CTId
1868                            console.log('hasTangkas.value: %s', hasTangkas.value);
1869                            if (typeof callback === 'function') {
1870                                callback({
1871                                    menuMap,
1872                                    hasReferral: response.hasReferral,
1873                                    isSWAccount: response.isSWAccount,
1874                                    CTId: +response.CTId,
1875                                    hasGameSubLigaSB: ligaSbSportSubmenuName.value !== '',
1876                                    isLigaSbOnly: response.isLigaSbOnly,
1877                                    //ligaSbSportSubmenuName: ligaSbSportSubmenuName.value,
1878                                    cacheVersion: cacheVersion.value,
1879                                    hasGamTypeLigaSB: menuMap['LIGASB'] !== undefined,
1880                                    hasGameNameTangkas: hasTangkas.value,
1881                                    sportBookDef: sportBookDef,
1882                                    sportBookType: sportBookType,
1883                                    sportBookTypeList: sportBookTypeList,
1884                                });
1885                            }
1886                        } else {
1887                            //alert(JSON.stringify(response));
1888                            console.error(JSON.stringify(response))
1889                        }
1890                    },
1891                    error: function (error) {
1892                        console.error('Error fetchMenuData():', error);
1893                    },
1894                });
1895            };
1896            const fetchMenuUrls = (callback) => {
1897                const queryString = window.location.search.substring(1);
1898                const apiUrl = 'public/GameGen.ashx?cmd=GetHeaderMenuUrls&' + queryString;
1899                $.ajax({
1900                    url: apiUrl,
1901                    method: 'GET',
1902                    contentType: 'application/json',
1903                    dataType: 'json',
1904                    success: function (response) {
1905                        if (response && response.success) {
1906                            Object.assign(menuUrls.value, response.urls);
1907                            if (typeof callback === 'function') {
1908                                callback(response);
1909                            }
1910                        } else {
1911                            console.log(response.text)
1912                        }
1913                    },
1914                    error: function (error) {
1915                        console.error('Error fetchMenuUrls() :', error);
1916                    },
1917                });
1918            };
1919            const genStyleSubMenu = (menuMap, mainMenuId) => {
1920                let tagParentSubMenu = document
1921                    .getElementById(mainMenuId)
1922                    .getAttribute('tag-parent-sub-menu');
1923                let htmlStyles = ``;
1924                for (const gameType in menuMap) {
1925                    htmlStyles += `${tagParentSubMenu}.${addPrefix(
1926                        gameType
1927                    )}${gameType.toLowerCase()}-game:hover > #sub-menu-${gameType.toLowerCase()} {display: inline-block}\r\n`;
1928                }
1929                //console.log(htmlStyles);
1930                const styleElement = document.createElement('style');
1931                styleElement.textContent = htmlStyles;
1932                document.body.appendChild(styleElement);
1933            };
1934            function handleMouseOver(e) {
1935                var className = $(e).attr('name');
1936                var height = $('#sub-menu-' + className).outerHeight();
1937                if (className === '365slot') $('#sub-menu-365slot').css('padding-bottom', '40px');
1938                changeHeaderHeight(HEADER_HEIGHT + height);
1939            }
1940
1941            const runScriptSubMenu = (menuMap) => {
1942                var listNameSubMenu = Object.keys(menuMap)
1943                listNameSubMenu = listNameSubMenu.filter(name => (menuMap[name] && menuMap[name].length >= 1 && menuMap[name][0].HasSubGame))
1944                listNameSubMenu = listNameSubMenu.map(name => `.${addPrefix(name)}${name.toLowerCase().replaceAll(" ", "-")}-game`);
1945
1946                $(`${listNameSubMenu.toString().trim()}`).mouseover(function () {
1947                    handleMouseOver($(this))
1948                });
1949                $(`${listNameSubMenu.toString().trim()}`).mouseout(function () {
1950                    // Fix for desktop site: when mousing over the SPORT submenu again after login, it will show.
1951                    // Need to add this statement because the submenu is automatically hidden for both desktop and mobile sites.
1952                    // By default, desktop uses CSS to hide/show the submenu. Therefore, hiding the submenu automatically needs to be handled by JavaScript code ($('#sub-menu-afbsb').hide() was called at runScriptUseSport3rdParty()), which will add a style attribute inline "style=display:none".
1953                    // Consequently, the submenu will not show when moused over again, as inline CSS takes priority over <style> tags and imported CSS files. Then it("style=display:none".) will be removed to reset everything as the start.
1954                    // This handling is deprecated due to the relocation of SPORT 1 and SPORT 2 to the main menu.
1955                    $('#sub-menu-afbsb').removeAttr('style');
1956
1957                    changeHeaderHeight(HEADER_HEIGHT)
1958                });
1959            };
1960            const runScriptSubMenuDefaultPage = (menuMap) => {
1961                var listNameSubMenu = Object.keys(menuMap)
1962                listNameSubMenu = listNameSubMenu.filter(name => (menuMap[name] && menuMap[name].length >= 1 && menuMap[name][0].HasSubGame))
1963                listNameSubMenu = listNameSubMenu.map(name => `.${addPrefix(name)}${name.toLowerCase().replaceAll(" ", "-")}-game`);
1964
1965                $(`${listNameSubMenu.toString().trim()}`).mouseover(function () {
1966                    var className = $(this).attr('name');
1967                    if (className === '365slot') $('#sub-menu-365slot').css('padding-bottom', '40px');
1968                    //console.log('%s: %s', className, height)
1969                });
1970            };
1971
1972            // This function is deprecated
1973            // It also addresses the issue where a user clicks on SPORT 2 before the AJAX call completes,
1974            // causing the "click" or "touchstart" event on the menu's anchor tag to misfire.
1975            // Use Vue's directive event handler (@click) with this function instead.
1976            /*const appendSWLigaSBScript = (isLigaSbOnly) => {
1977                $('a').on('click touchstart', function () {
1978                    try {
1979                        var parent = window.parent.document;
1980                        var href = $(this).attr('href')
1981                        var target = $(this).attr('target')
1982                        //console.log(href)
1983                        if (href !== undefined && href !== '#' && target !== '_blank') {
1984                            if (href.toLowerCase().indexOf('lobbygames.aspx?type=afbsb') > -1) {
1985                                $(parent).find('.divPanel').hide('fast');
1986                                let width = isMobileDevice() ? 'width: 100% !important;' : 'width: 1024px !important;';
1987                                //console.log(width);
1988                                $(parent).find('.divMain')[0].style.cssText = width;
1989                            } else {
1990                                if (isLigaSbOnly) {
1991                                    let width = isMobileDevice() ? 'width: 100% !important;' : 'width: 1024px !important;';
1992                                    //console.log(width);
1993                                    $(parent).find('.divMain')[0].style.cssText = width;
1994                                }
1995                                else {
1996                                    $(parent).find('.divPanel').show('fast');
1997                                    $(parent).find('.divMain').css('width', 'calc(100% - 205px)');
1998                                }
1999                                // reset
2000                                // seem this one is deprecated
2001                                localStorage.setItem('ligaSbHost', '')
2002                                $('#accTpLst', window.parent.frames["fraHeader"].document).attr('onchange','top.location.href = this.form.accTpLst.options[this.form.accTpLst.selectedIndex].value; return false;')
2003                            }
2004
2005                            // This handling is deprecated due to the relocation of SPORT 1 and SPORT 2 to the main menu.
2006                            // hide submenu of sport-game when click to other game on main menu
2007                            if (isHeaderPage()) {
2008                                setTimeout(function () {
2009                                    $('#sub-menu-afbsb').hide();
2010                                    changeHeaderHeight(HEADER_HEIGHT)
2011                                }, 500)
2012                                $('#sub-menu-afbsb').on('touchstart', function (event) {
2013                                    event.stopPropagation();
2014                                });
2015                            }
2016
2017                            var fraMainIframe = parent.querySelector('iframe[name="fraMain"]');
2018                            if (fraMainIframe.onload === null)
2019                                fraMainIframe.onload = function () {
2020                                    if (href.indexOf('LobbyGames.aspx?type=AFBSB') === -1) {
2021                                        //$(parent).find('.divPanel').show('fast');
2022                                        //$(parent).find('.divMain').css('width', 'calc(100% - 205px)');
2023                                    }
2024                                    //fraMainIframe.onload = null;
2025                                }
2026
2027                            if (isMobileDevice())
2028                                fraMainIframe.src = href;
2029                        }
2030                    }
2031                    catch (e) {
2032                        console.log(e)
2033                    }
2034                });
2035                if (isHeaderPage()) {
2036                    if (!isLigaSbOnly) {
2037                        $('.afbsb-game').on('touchstart', function (event) {
2038                            event.preventDefault();
2039                            var subMenu = $(this).find('.sub-menu');
2040                            subMenu.toggle();
2041                            if (subMenu.is(':visible'))
2042                                handleMouseOver($(this))
2043                            else
2044                                changeHeaderHeight(HEADER_HEIGHT)
2045                        });
2046
2047                        $('#sub-menu-afbsb').on('touchstart', function (event) {
2048                            event.stopPropagation();
2049                        });
2050                    }
2051                }
2052            } */
2053
2054            // will be deprecated when moving SPORT 2 to main menu is done
2055            const appendTWLigaSBScript = (CTId, cacheVersion) => {
2056                let arrowType = 'text';
2057                switch (CTId) {
2058                    //case 54:
2059                    case 64:
2060                    case 114:
2061                    case 264:
2062                    case 265:
2063                        arrowType = 'span'; break;
2064                    case 52:
2065                    //case 57:
2066                    case 69:
2067                    case 87:
2068                    //case 109:
2069                    case 123:
2070                    case 256:
2071                    case 263:
2072                    case 279: // SW WL
2073                        arrowType = 'div'; break;
2074                    case 99:
2075                    case 100:
2076                        arrowType = 'none'; break;
2077                }
2078                let aTagIsParentMenu = false
2079                switch (CTId) {
2080                    case 85:
2081                    case 124:
2082                    case 135:
2083                    case 208:
2084                    case 218:
2085                    case 227:
2086                    case 244:
2087                    case 245:
2088                    case 260:
2089                        aTagIsParentMenu = true; break;
2090                }
2091                var scriptTagArrow = $('<script></script>').attr('type', 'text/javascript').html(`var arrowOption = '${arrowType}'; var aTagIsParentMenu = ${aTagIsParentMenu}`);
2092                $(mountEl).append(scriptTagArrow);
2093                var scriptTagSubmenuGen = $('<script></script>').attr({
2094                    type: 'text/javascript',
2095                    src: 'js/header.submenu.gen.js?v=' + cacheVersion
2096                });
2097                $(mountEl).append(scriptTagSubmenuGen);
2098            }
2099
2100            const isExistReferralAuto = () => {
2101                try {
2102                    const listItems = $(mountEl).find('li');
2103                    for (let i = 0; i < listItems.length; i++) {
2104                        let aTag = $(listItems[i]).find('a');
2105                        if (aTag && aTag.attr('href') && aTag.attr('href').toLowerCase() === 'lobbygames.aspx?type=referral') {
2106                            return true;
2107                        }
2108                    }
2109                    return false;
2110                } catch (error) {
2111                    console.log(error);
2112                    return false;
2113                }
2114            }
2115
2116            const removeDuplicatedReferralMenu = () => {
2117                $(mountEl).find('li').each(function () {
2118                    let aTag = $(this).find('a')
2119                    if (aTag && aTag.attr('href') && aTag.attr('href').toLowerCase() === '_view/referral.aspx') {
2120                        $(this).remove();
2121                    }
2122                });
2123            }
2124            // move whitelist WL from Util.Header.Menu.cs function IsActivatedSubMenuSport
2125            const manualAppendLigaSBScriptCTIds = [];
2126
2127            onMounted(() => {
2128                // Save fraBodyFraMainHref to sessionStorage on page unload (F5/navigate away)
2129                // Only if fraMain is same-domain accessible; cross-domain throws and is silently ignored
2130                window.addEventListener('beforeunload', function () {
2131                    try {
2132                        var fraMain = window.parent.frames['fraBody'].frames['fraMain'];
2133                        var href = fraMain.location.href;
2134                        if (href && href !== 'about:blank')
2135                            sessionStorage.setItem('fraBodyFraMainHref', href);
2136                    } catch (e) { /* cross-domain: ignore */ }
2137                });
2138
2139                try {
2140                    // --- Helpers ---
2141                    const $qs = (sel, root = document) => root.querySelector(sel);
2142                    const $qsa = (sel, root = document) => Array.from(root.querySelectorAll(sel));
2143
2144                    const replaceImgMenuLoading = () => {
2145                        $qsa('img.menu-s12-loading').forEach(img => img.remove());
2146                    };
2147
2148                    const goToHome = async (event) => {
2149                        event.preventDefault();
2150                        const link = event.currentTarget;
2151                        const href = link.getAttribute('href') || '';
2152                        const queryString = href.includes('?') ? href.split('?')[1] : '';
2153                        try {
2154                            const resp = await fetch('Handlers/MainGuestHandler.ashx?p=h', { method: 'GET' });
2155                            // bạn vẫn console như cũ
2156                            console.log('Server response ok:', resp.ok);
2157
2158                            const origin = window.parent.location.origin.replace(/\/+$/, '');
2159                            const pathParts = window.parent.location.pathname.split('/').filter(p => p);
2160                            const firstPath = pathParts.length > 0 ? pathParts[0] : '';
2161                            const search = queryString ? '?' + queryString : '';
2162                            window.parent.location.href = `${origin}/${firstPath}${search}`;
2163                        } catch (xhr) {
2164                            console.error('AJAX error (fetch):', xhr);
2165                        }
2166                    };
2167
2168                    // --- Local cache / init ---
2169                    //const mountEl = document.body; // or root DOM mount of component
2170                    const menuMapKey = isGuest.value ? 'menuMap' : 'menuMap_user';
2171                    if (localStorage.getItem(menuMapKey)) {
2172                        let menuMap = JSON.parse(localStorage.getItem(menuMapKey));
2173                        menuMap = filterMenuMapBySportBookType(menuMap, getSportBookTypeList());
2174                        if (isGuest.value || !isHeaderPage()) {
2175                            delete menuMap['ALLGAMES'];
2176                            delete menuMap[refText];
2177                        }
2178                        syncMenuMap(data.value, menuMap);
2179                        scheduleHeaderMenuScrollInit();
2180                    }
2181
2182                    const isSWAccount = localStorage.getItem('isSWAccount') === 'true';
2183                    const CTId = localStorage.getItem('CTId');
2184                    //const cacheVersion = localStorage.getItem('cacheVersion') || Date.now();
2185                    const sportBookDef = +(localStorage.getItem('sportBookDef') || 2);
2186                    const ligaSbHost = localStorage.getItem('latestSPORTHost');
2187
2188                    // --- Logged-in branch ---
2189                    if (!isGuest.value) {
2190                        if (CTId && !isSWAccount) {
2191
2192                            //if (!isGuest.value && !isSWAccount && getSportBookType() === 0)
2193                            //    addSPORT2Menu(CTId, cacheVersion);
2194
2195                            removeTANGKAS();
2196
2197                            if (isSWAccount)
2198                                replaceImgMenuLoading();
2199                            if (!isSportOnly(1)) {
2200                                if (sportBookDef !== 1) {
2201                                    setTimeout(() => replaceTargetFraBody(), 111);
2202                                }
2203                                else {
2204                                    try {
2205                                        hrefFraBody = parent.frames['fraBody'].location.href;
2206                                    }
2207                                    catch (e) {
2208                                        setTimeout(() => replaceTargetFraBody(), 111);
2209                                    }
2210                                }
2211                            }
2212                        }
2213                        else if (isSWAccount) {
2214                            if (!isSportOnly(1))
2215                                setTimeout(() => replaceTargetFraBody(), 111);
2216
2217                            // a:not([vue-click]) -> click handler
2218                            $qsa('a:not([vue-click])').forEach(a => {
2219                                a.addEventListener('click', (event) => {
2220                                    try {
2221                                        const href = a.getAttribute('href');
2222                                        const target = a.getAttribute('target');
2223                                        const hasOnclick = a.getAttribute('onclick') != null;
2224
2225                                        if (!hasOnclick && target !== '_blank' && target !== '_top' && target !== '_parent') {
2226                                            event.preventDefault();
2227                                        }
2228
2229                                        if (target === 'fraMain') {
2230                                            window.open(href, target);
2231                                        } else if (target === 'fraBody') {
2232                                            if (isFraMainLoaded()) {
2233                                                window.open(href, 'fraMain');
2234                                            } else {
2235                                                window.open('Body.aspx?href=' + href + getLpnlParam(), target);
2236                                            }
2237                                        }
2238                                    } catch (e) {
2239                                        console.log(e);
2240                                    }
2241                                });
2242                            });
2243                        }
2244                    }
2245
2246                    // --- Guest header home link handling ---
2247                    if (isGuest.value && isHeaderPage()) {
2248                        const firstLink = $qs('a', mountEl);
2249                        const href = firstLink?.getAttribute('href') || '';
2250                        const text = (firstLink?.textContent || '').trim().toLowerCase();
2251                        const isLocalHomePath = /^\/[a-z0-9_]+$/i.test(href);
2252                        const isLikelyHomeLink = href === '/' || isLocalHomePath || text === 'home' || text === '';
2253
2254                        if (firstLink && isLikelyHomeLink) {
2255                            firstLink.addEventListener('click', goToHome);
2256                        }
2257
2258                        const anchorHome = $qsa('a[target="_parent"]').filter(a => {
2259                            const aHref = a.href;
2260                            const baseUrl = window.location.origin + window.location.pathname;
2261                            const currentBase = window.location.origin + '/' + window.location.pathname.split('/')[1] + '/';
2262                            return aHref === window.location.origin + '/' || aHref === currentBase;
2263                        });
2264                        anchorHome.forEach(a => a.addEventListener('click', goToHome));
2265                    }
2266                    if (!isGuest.value && isHeaderPage()) {
2267                        var btnSignout = document.querySelector('a[href*="signout.aspx"]');
2268                        if (!btnSignout) return;
2269                        btnSignout.addEventListener('click', function (e) {
2270                            e.preventDefault();
2271                            try {
2272                                var f = window.parent && window.parent.frames && window.parent.frames['fraBody'];
2273                                if (f) f.postMessage({ ACT: 'LogoutAll', value: null }, '*');
2274                            } catch (err) { }
2275                            //setTimeout(function () {
2276                            window.top.location.href = btnSignout.href;
2277                            //}, 50);
2278                        });
2279                    }
2280                    // --- Fetch menu & run scripts ---
2281                    fetchMenuData(({ menuMap, isSWAccount: sw, hasReferral, CTId: ct, hasGameSubLigaSB, isLigaSbOnly, cacheVersion: cv, hasGamTypeLigaSB, hasGameNameTangkas }) => {
2282                        genStyleSubMenu(menuMap, appId.value);
2283
2284                        if (isHeaderPage()) {
2285                            if (isGuest.value)
2286                                runScriptSubMenu(menuMap);
2287                            if (!isGuest.value && !sw)
2288                                addAllSportMenus(ct, cv);
2289
2290                            setTimeout(() => {
2291                                if (!isGuest.value && hasReferral && isExistReferralAuto()) {
2292                                    removeDuplicatedReferralMenu();
2293                                }
2294
2295                                if (!isGuest.value && !sw && !manualAppendLigaSBScriptCTIds.includes(ct)) {
2296                                    // Submenu
2297                                    if (hasGameSubLigaSB) {
2298                                        appendTWLigaSBScript(ct, cv);
2299                                    }
2300                                    // Main menu (specific CTs that serve LIGASB from game data)
2301                                    else if (hasGamTypeLigaSB) {
2302                                        if (!localStorage.getItem('CTId'))
2303                                        //|| localStorage.getItem('isSWAccount') === 'true')
2304                                        {
2305                                            //addAllSportMenus(ct, cv);
2306
2307                                            if (!isSportOnly(1))
2308                                                replaceTargetFraBody();
2309                                        }
2310                                        removeTANGKAS();
2311                                        if (hasGameNameTangkas)
2312                                            replaceSLOTUrl();
2313                                    }
2314                                }
2315                                // SW WLs / guest-like
2316                                else if (isGuest.value || (sw && !localStorage.getItem('isSWAccount'))) {
2317                                    if (!isSportOnly(1))
2318                                        replaceTargetFraBody();
2319                                    $qsa('a:not([vue-click])').forEach(a => {
2320                                        const handler = (event) => {
2321                                            try {
2322                                                const href = a.getAttribute('href');
2323                                                const target = a.getAttribute('target');
2324                                                const hasOnclick = a.getAttribute('onclick') != null;
2325
2326                                                if (!hasOnclick && target !== '_blank' && target !== '_top' && target !== '_parent') {
2327                                                    event.preventDefault();
2328                                                }
2329                                                if (target === 'fraMain') {
2330                                                    window.open(href, target);
2331                                                } else if (target === 'fraBody') {
2332                                                    if (isFraMainLoaded()) {
2333                                                        window.open(href, 'fraMain');
2334                                                    } else {
2335                                                        window.open('Body.aspx?href=' + href + getLpnlParam(), target);
2336                                                    }
2337                                                }
2338                                            } catch (e) {
2339                                                console.log(e);
2340                                            }
2341                                        };
2342                                        a.addEventListener('click', handler);
2343                                        a.addEventListener('touchstart', handler, { passive: false });
2344                                    });
2345                                }
2346
2347                                localStorage.setItem('CTId', ct);
2348                                localStorage.setItem('cacheVersion', cv);
2349                                localStorage.setItem('isSWAccount', sw);
2350                                localStorage.setItem('sportBookDef', sportBookDef);
2351                                // Init selected sport from default if not already set this session
2352                                if (!sessionStorage.getItem('sportBookSelected'))
2353                                    setSelectedSport(sportBookDef);
2354                                applyAddMHClones();
2355                                scheduleHeaderMenuScrollInit();
2356                            }, 555);
2357                        } else {
2358                            runScriptSubMenuDefaultPage(menuMap);
2359                            applyAddMHClones();
2360                            scheduleHeaderMenuScrollInit();
2361                        }
2362                    });
2363
2364                    // --- After login always run at header page ---
2365                    if (!isGuest.value) {
2366                        // document ready as DOMContentLoaded (with Vue, onMounted was safe)
2367                        fetchMenuUrls();
2368                        replaceImgMenuLoading();
2369                        getMailInfo();
2370
2371                        // accTpLst trong iframe header
2372                        const fraHeader = parent.frames['fraHeader'];
2373                        const fraHeaderDocument = fraHeader?.document || null;
2374
2375                        if (fraHeaderDocument) {
2376                            const accSel = fraHeaderDocument.querySelector('#accTpLst');
2377                            const ch = 'BroadcastChannel' in window ? new BroadcastChannel('accTypeChannel') : null;
2378                            // Unique ID for each page to prevent message echo loops
2379                            const PAGE_ID = (crypto?.randomUUID?.() || Math.random().toString(36).slice(2));
2380                            ch.onmessage = (event) => {
2381                                const data = event.data || {};
2382                                if (data?.ACT === 'switch_acctype_fraheader') {
2383                                    if (data.source === PAGE_ID) return; // ignore self messages
2384                                    const isLocal = location.host.includes('localhost');
2385                                    let hostValue = isLocal ? location.pathname.replace(/\/Header\.aspx$/i, '') : '';
2386                                    try {
2387                                        accSel.value = `${hostValue}/?accType=${data.value}`;
2388                                        accSel.dispatchEvent(new Event('change', { bubbles: true }));
2389                                    }
2390                                    catch (e) { console.log(e) }
2391                                }
2392                                console.log('[BC] Home received:', data);
2393                            };
2394                            if (accSel) {
2395                                accSel.removeAttribute('onchange');
2396                                accSel.addEventListener('change', (e) => {
2397                                    const raw = accSel.value || '';
2398                                    const accType = raw.slice(-2);
2399                                    let hrefFraBody = '';
2400                                    try {
2401                                        // check use is using SPORT 1 or SPORT 2,3
2402                                        hrefFraBody = parent.frames['fraBody'].location.href;
2403                                        const lpnl = new URLSearchParams(hrefFraBody).get("lpnl");
2404                                        let gameType = '';
2405                                        // if SPORT 1 is using. . Case fraBody href is SPORT 1
2406                                        try {
2407                                            // check fraMain iframe is accessible (same domain) (sometimes POKER from 3rd party URL)
2408                                            const hrefMain = parent.frames['fraBody'].frames['fraMain'].location.href;
2409                                            try {
2410                                                // handler for SPORT 1 same domain fraMain iframe
2411                                                const fraPanel = parent.frames['fraBody'].frames['fraPanel'];
2412                                                const hrefPanel = fraPanel.location.href;
2413                                                parent.frames['fraBody'].frames['fraMain'].location.href = `_View/RMOdds1.aspx?accType=${accType}`
2414                                            }
2415                                            catch (err) {
2416                                                // handle for SPORT 2/3 same domain fraMain iframe (fraMain page as Dep, Wash...)
2417                                                switch (lpnl) {
2418                                                    case "lsb": gameType = "AFBSB"; break;
2419                                                    case "omsb": gameType = "OMSB"; break;
2420                                                }
2421                                                document.querySelector(`a[href="LobbyGames.aspx?type=${gameType}"]`)?.click();
2422                                                console.log('Can not access DOM fraPanel:', err);
2423                                            }
2424                                            const programmatic = e.isTrusted === false;
2425                                            if (!programmatic && ch) {
2426                                                // User-triggered → broadcast change to other pages
2427                                                let data = {
2428                                                    ACT: 'switch_acctype_framain',
2429                                                    value: accType,
2430                                                    source: PAGE_ID,
2431                                                    ts: Date.now()
2432                                                }
2433                                                ch.postMessage(data);
2434                                                console.log('[BC] Home sent:', data);
2435                                            }
2436                                        }
2437                                        catch (e) {
2438                                            // fraMain iframe is not accessible (diff domain)
2439                                            try {
2440                                                // handler for SPORT 1 different domain fraMain iframe
2441                                                const fraPanel = parent.frames['fraBody'].frames['fraPanel'];
2442                                                const hrefPanel = fraPanel.location.href;
2443                                                parent.frames['fraBody'].frames['fraMain'].location.href = `_View/RMOdds1.aspx?accType=${accType}`
2444                                            }
2445                                            catch (err) {
2446                                                // handler for SPORT 2/3 different domain fraMain iframe as FISH POKER, TOGEL ... 3rd party URL
2447                                                switch (lpnl) {
2448                                                    case "lsb": gameType = "AFBSB"; break;
2449                                                    case "omsb": gameType = "OMSB"; break;
2450                                                }
2451                                                document.querySelector(`a[href="LobbyGames.aspx?type=${gameType}"]`)?.click();
2452                                                console.log('Can not access DOM fraPanel:', err);
2453                                            }
2454                                            console.log('Can not access DOM fraMain:', e);
2455                                        }
2456                                    } catch (e) {
2457                                        // handle for SPORT 2 same domain
2458                                        if (hrefFraBody === '') {
2459                                            let latestSPORTHost = localStorage.getItem('latestSPORTHost');
2460                                            console.log('changeOddStyle via postMessage to:', latestSPORTHost);
2461                                            const sentObj = { ACT: 'ChangeOddsType', value: accType };
2462                                            // parent.frames['fraBody'].postMessage(JSON.stringify(sentObj), 'https://' + latestSPORTHost);
2463                                            // change to * for working well on multi tab with multi sport page
2464                                            parent.frames['fraBody'].postMessage(JSON.stringify(sentObj), '*');
2465                                        }
2466                                        else {
2467                                            // handle for SPORT 1 different domain
2468                                            // todo : writedown test case for this scenario, don't rmb how to trigger this case
2469                                            if (hrefFraBody !== '')
2470                                                parent.frames['fraBody'].frames['fraMain'].location.href = `_View/RMOdds1.aspx?accType=${accType}`
2471                                            else // handle for SPORT 2 different domain
2472                                                document.querySelector('a[href="LobbyGames.aspx?type=AFBSB"]')?.click();
2473                                        }
2474                                    }
2475                                });
2476                            }
2477                        }
2478                    }
2479
2480                    // --- Language change (header page) ---
2481                    if (isHeaderPage()) {
2482                        const fraHeader = parent.frames['fraHeader'];
2483                        const fraHeaderDocument = fraHeader?.document || null;
2484
2485                        if (fraHeaderDocument) {
2486                            const langEls = $qsa('#langAfter, #lstLang, #lstLang2', fraHeaderDocument);
2487                            langEls.forEach(el => el.removeAttribute('onchange'));
2488                            langEls.forEach(el => {
2489                                el.addEventListener('change', () => {
2490                                    let lang = (fraHeaderDocument.querySelector('#langAfter')?.value
2491                                        || fraHeaderDocument.querySelector('#lstLang')?.value
2492                                        || fraHeaderDocument.querySelector('#lstLang2')?.value || '').slice(-5);
2493
2494                                    let hrefFraBody = '';
2495                                    try {
2496                                        hrefFraBody = parent.frames['fraBody'].location.href;
2497                                        const lpnl = new URLSearchParams(hrefFraBody).get("lpnl");
2498                                        let gameType = '';
2499                                        // Handle for SPORT 1 is using. Case fraBody href is SPORT 1
2500                                        try {
2501                                            // check fraMain iframe is accessible (same domain) (sometimes POKER from 3rd party URL)
2502                                            const hrefMain = parent.frames['fraBody'].frames['fraMain'].location.href;
2503                                            // if hrefMain is accessible, continue from here
2504                                            const fraPanel = parent.frames['fraBody'].frames['fraPanel'];
2505                                            try {
2506                                                // handle for SPORT 1 left panel and fraMain is 3rd party URL as POKER, TOGEL ...
2507                                                const url = fraPanel.location.href;
2508                                                window.parent.postMessage({ ACT: 'switch_lang', LANG: lang }, '*');
2509
2510                                            } catch (e) {
2511
2512                                                // handle for SPORT 2,3 left panel and fraMain is 3rd party URL as POKER, TOGEL ...
2513                                                console.log('Can not access DOM fraPanel:', e);
2514                                                switch (lpnl) {
2515                                                    case "lsb": gameType = "AFBSB"; break;
2516                                                    case "omsb": gameType = "OMSB"; break;
2517                                                }
2518                                                fraPanel.location.href = `LobbyGames.aspx?type=${gameType}&hodds=1&lang=` + lang;
2519                                                window.parent.postMessage({ ACT: 'switch_lang_framain', LANG: lang }, '*');
2520                                            }
2521                                        }
2522                                        catch (e) {
2523                                            // Handle for SPORT 2,3 is using. Case fraBody href is SPORT 2, 3
2524                                            // fraMain iframe is not accessible (diff domain)
2525                                            try {
2526                                                // handle for SPORT 1 different domain fraMain iframe
2527                                                const fraPanel = parent.frames['fraBody'].frames['fraPanel'];
2528                                                const hrefPanel = fraPanel.location.href; // this is statement to test to break to catch or not
2529                                                window.parent.postMessage({ ACT: 'switch_lang', LANG: lang }, '*');
2530                                            }
2531                                            catch (err) {
2532                                                // handle for SPORT 2,3 different domain fraMain iframe
2533                                                console.log('Can not access DOM fraPanel:', err);
2534                                                let gameType = '';
2535                                                switch (lpnl) {
2536                                                    case "lsb": gameType = "AFBSB"; break;
2537                                                    case "omsb": gameType = "OMSB"; break;
2538                                                }
2539                                                parent.frames['fraBody'].frames['fraPanel'].location.href = 'LobbyGames.aspx?type=' + gameType + '&hodds=1&lang=' + lang;
2540                                                window.parent.postMessage({ ACT: 'switch_lang_framain', LANG: lang }, '*');
2541                                            }
2542                                            console.log('Can not access DOM fraMain:', e);
2543                                        }
2544                                    } catch (e) {
2545                                        // if SPORT 2 is using. Case fraBody href is SPORT 2, 3
2546                                        try {
2547                                            //console.log('ChangeLanguage via postMessage to:', ligaSbHost);
2548                                            //console.log('hrefFraBody:', hrefFraBody);
2549                                            //console.log('ligaSbHost:', ligaSbHost);
2550
2551                                            if (hrefFraBody === '') {
2552                                                let latestSPORTHost = localStorage.getItem('latestSPORTHost');
2553                                                console.log('changelang via postMessage to:', latestSPORTHost);
2554                                                const sentObj = { ACT: 'ChangeLanguage', value: lang };
2555                                                //window.parent.frames['fraBody'].postMessage(JSON.stringify(sentObj), 'https://' + latestSPORTHost);
2556                                                // change to * for working well on multi tab with multi sport page
2557                                                window.parent.frames['fraBody'].postMessage(JSON.stringify(sentObj), '*');
2558                                            } else {
2559                                                // todo : writedown test case for this scenario, don't rmb how to trigger this case
2560                                                parent.frames['fraBody'].frames['fraPanel'].location.href = 'LobbyGames.aspx?type=AFBSB&hodds=1&lang=' + lang;
2561                                                window.parent.postMessage({ ACT: 'switch_lang_framain', LANG: lang }, '*');
2562                                            }
2563                                            //window.parent.frames['fraHeader'].location.href = `Header.aspx?lang=${lang}`;
2564
2565                                        } catch (err) {
2566                                            console.log(err);
2567                                        }
2568                                    }
2569                                    window.location.href = 'Header.aspx?lang=' + lang
2570
2571                                });
2572                            });
2573                        }
2574                    }
2575                    // --- guest common handling ---
2576                    //$(atob('I2hlbHAtbGluaw==')).on('click', function (e) {
2577                    //    $.ajax({
2578                    //        type: "POST",
2579                    //        url: atob('SGFuZGxlcnMvTExDSGFuZGxlci5hc2h4'),
2580                    //    });
2581                    //});
2582                } catch (error) {
2583                    console.log(error);
2584                }
2585            });
2586
2587            onBeforeMount(() => {
2588                appId.value = mountEl.dataset.id;
2589                isGuest.value = mountEl.dataset.isGuest === "true";
2590
2591                if (mountEl.dataset.hideSubmenuMobileResponsive)
2592                    hideSubmenuMobileResponsive.value = mountEl.dataset.hideSubmenuMobileResponsive === "true"
2593                if (mountEl.dataset.hiddenReferralMenu)
2594                    hiddenReferralMenu.value = mountEl.dataset.hiddenReferralMenu === "true"
2595
2596                cacheVersion.value = +getTimestampServerCache();
2597                //console.log(mountEl.dataset.id);
2598                //console.log(mountEl.dataset.isGuest);
2599                //console.log(mountEl.dataset.hideSubmenuMobileResponsive);
2600                //console.log(hideSubmenuMobileResponsive.value);
2601            });
2602            const computedProps = {};
2603            return {
2604                menus: data.value,
2605                menuUrls: menuUrls.value,
2606                dataFetched,
2607                ...computedProps,
2608                cacheVersion,
2609                isGuest,
2610                hideSubmenuMobileResponsive,
2611                hiddenReferralMenu,
2612            };
2613        },
2614    });
2615}
2616var menu = createMenu();
2617menu.component('image-item', ImageItem);
2618menu.component('down-arrow', DownArrow);
2619menu.component('menu-item', MenuItem);
2620menu.component('submenu-container', SubMenuContainer);
2621menu.component('submenu-item', SubMenuItem);
2622menu.component('submenu', SubMenu);

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.