PageSourceSearch

https://cdn.azmen.com/js/new-firebase-init/v29/new-firebase-init.js

js azmen.com collected 2026-09-28 06:30:45 UTC 112,287 bytes, 2,315 lines download raw bytes

1import {initializeApp} from 'https://www.gstatic.com/firebasejs/10.5.0/firebase-app.js';
2import {getAuth, connectAuthEmulator, onAuthStateChanged, signOut, sendEmailVerification, signInWithCustomToken} from 'https://www.gstatic.com/firebasejs/10.5.0/firebase-auth.js';
3
4const app = initializeApp({
5    apiKey: 'AIzaSyAprP1AGzXG5jID7UFJD0lpwTZYRgYnq58',
6    authDomain: 'aznude-fea6a.firebaseapp.com',
7    databaseURL: 'https://aznude-fea6a.firebaseio.com',
8    projectId: 'aznude-fea6a',
9    storageBucket: 'aznude-fea6a.appspot.com',
10    messagingSenderId: '168249351670',
11});
12
13const auth = getAuth(app);
14
15// Separate Auth instance from src/firebase.js (this one's loaded straight from the
16// gstatic CDN, not bundled via Vite) — needs its own emulator redirect, same reasoning,
17// and the same ?backend=prod / localStorage override (see src/firebase.js for why).
18const backendParam = new URLSearchParams(location.search).get('backend');
19if (backendParam === 'prod' || backendParam === 'local') {
20    localStorage.setItem('aznude:backendTarget', backendParam);
21}
22const BACKEND_OVERRIDE = localStorage.getItem('aznude:backendTarget');
23
24const IS_LOCAL = (location.hostname === 'localhost' || location.hostname === '127.0.0.1')
25    && BACKEND_OVERRIDE !== 'prod';
26if (IS_LOCAL) {
27    connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true });
28}
29
30// /**
31//  * Cross-Domain Authentication Module
32//  * Handles seamless authentication across aznude.com, azmen.com, azfans.com, and aznudefeet.com
33//  */
34// class CrossDomainAuth {
35//     constructor(config) {
36//         this.backendUrl = `${PRIMARY_BASE}/auth`;
37//         this.firebase = config.firebase;
38//         this.debug = config.debug || false;
39//     }
40
41//     async createSession(user) {
42//         try {
43//             const token = await user.getIdToken();
44            
45//             if (this.debug) {
46//                 console.log('[CrossDomainAuth] Creating session for user:', user.uid);
47//             }
48
49//             const response = await fetch(`${this.backendUrl}/create-session`, {
50//                 method: 'POST',
51//                 credentials: 'include',
52//                 headers: {
53//                     'Content-Type': 'application/json',
54//                 },
55//                 body: JSON.stringify({ token: token })
56//             });
57
58//             if (!response.ok) {
59//                 throw new Error(`Failed to create session: ${response.statusText}`);
60//             }
61
62//             const data = await response.json();
63            
64//             if (this.debug) {
65//                 console.log('[CrossDomainAuth] Session created:', data);
66//             }
67
68//             return data;
69//         } catch (error) {
70//             console.error('[CrossDomainAuth] Error creating session:', error);
71//             throw error;
72//         }
73//     }
74
75//     async checkAndRestoreSession() {
76//         try {
77//             const currentUser = this.firebase.currentUser;
78//             if (currentUser) {
79//                 if (this.debug) {
80//                     console.log('[CrossDomainAuth] User already signed in locally');
81//                 }
82//                 return currentUser;
83//             }
84
85//             if (this.debug) {
86//                 console.log('[CrossDomainAuth] Checking for cross-domain session...');
87//             }
88
89//             const response = await fetch(`${this.backendUrl}/check-session`, {
90//                 method: 'GET',
91//                 credentials: 'include',
92//             });
93
94//             if (!response.ok) {
95//                 if (this.debug) {
96//                     console.log('[CrossDomainAuth] No valid session found');
97//                 }
98//                 return null;
99//             }
100
101//             const data = await response.json();
102            
103//             if (this.debug) {
104//                 console.log('[CrossDomainAuth] Valid session found, signing in with custom token...');
105//             }
106
107//             const userCredential = await signInWithCustomToken(this.firebase, data.custom_token);
108            
109//             if (this.debug) {
110//                 console.log('[CrossDomainAuth] Successfully restored session for:', userCredential.user.email);
111//             }
112
113//             return userCredential.user;
114//         } catch (error) {
115//             if (this.debug) {
116//                 console.log('[CrossDomainAuth] Error checking session:', error.message);
117//             }
118//             return null;
119//         }
120//     }
121
122//     async deleteSession() {
123//         try {
124//             if (this.debug) {
125//                 console.log('[CrossDomainAuth] Deleting session...');
126//             }
127
128//             const response = await fetch(`${this.backendUrl}/delete-session`, {
129//                 method: 'POST',
130//                 credentials: 'include',
131//             });
132
133//             if (!response.ok) {
134//                 throw new Error(`Failed to delete session: ${response.statusText}`);
135//             }
136
137//             await signOut(this.firebase);
138            
139//             if (this.debug) {
140//                 console.log('[CrossDomainAuth] Session deleted and user signed out');
141//             }
142
143//             return true;
144//         } catch (error) {
145//             console.error('[CrossDomainAuth] Error deleting session:', error);
146//             throw error;
147//         }
148//     }
149
150//     async signInWithEmailAndPassword(email, password) {
151//         try {
152//             const { signInWithEmailAndPassword: firebaseSignIn } = await import('https://www.gstatic.com/firebasejs/10.5.0/firebase-auth.js');
153//             const userCredential = await firebaseSignIn(this.firebase, email, password);
154//             await this.createSession(userCredential.user);
155//             return userCredential.user;
156//         } catch (error) {
157//             console.error('[CrossDomainAuth] Login error:', error);
158//             throw error;
159//         }
160//     }
161
162//     async signOut() {
163//         return await this.deleteSession();
164//     }
165// }
166
167// // Initialize cross-domain auth
168// const crossDomainAuth = new CrossDomainAuth({
169//     firebase: auth,
170//     debug: false // Set to true for debugging
171// });
172
173// // Make it globally available
174// window.crossDomainAuth = crossDomainAuth;
175
176/* SENTRY LAZY LOADER SHIM (imports on demand) */
177let __sentryModule = null;
178let __sentryInitOptions = null;
179let __sentryQueue = [];
180let __sentryLoading = null;
181let __sentryWantTracing = false;
182let __sentryWantReplay  = false;
183let __sentryConsoleCfg  = null;
184
185function __sentryCloneWithoutIntegrations(opts) {
186    const base = {};
187    if (!opts) return base;
188    for (const k in opts) {
189        if (Object.prototype.hasOwnProperty.call(opts, k) && k !== 'integrations') {
190            base[k] = opts[k];
191        }
192    }
193    return base;
194}
195
196function __sentryBuildIntegrations(mod) {
197    const ints = [];
198    try {
199        if (__sentryWantTracing && typeof mod.browserTracingIntegration === 'function') {
200            ints.push(mod.browserTracingIntegration());
201        }
202    } catch (_e) {}
203    try {
204        if (__sentryWantReplay && typeof mod.replayIntegration === 'function') {
205            ints.push(mod.replayIntegration());
206        }
207    } catch (_e) {}
208    try {
209        if (__sentryConsoleCfg && typeof mod.consoleLoggingIntegration === 'function') {
210            ints.push(mod.consoleLoggingIntegration(__sentryConsoleCfg));
211        }
212    } catch (_e) {}
213    return ints;
214}
215
216function __sentryLoad() {
217    if (__sentryModule) return Promise.resolve(__sentryModule);
218    if (__sentryLoading) return __sentryLoading;
219
220    __sentryLoading = import('https://cdn.jsdelivr.net/npm/@sentry/browser@9/+esm')
221        .then(mod => {
222            __sentryModule = mod;
223            try {
224                const base = __sentryCloneWithoutIntegrations(__sentryInitOptions);
225                base.integrations = __sentryBuildIntegrations(mod);
226                mod.init(base);
227            } catch (_e) {}
228            const queued = __sentryQueue.slice();
229            __sentryQueue.length = 0;
230            for (let i = 0; i < queued.length; i++) {
231                try { queued[i](mod); } catch (_e) {}
232            }
233            return mod;
234        })
235        .catch(_err => {
236            return null;
237        });
238
239    return __sentryLoading;
240}
241
242const Sentry = {
243    init: function (options) {
244        __sentryInitOptions = options || {};
245    },
246    browserTracingIntegration: function () {
247        __sentryWantTracing = true;
248        return {};
249    },
250    replayIntegration: function () {
251        __sentryWantReplay = true;
252        return {};
253    },
254    consoleLoggingIntegration: function (cfg) {
255        __sentryConsoleCfg = cfg || {};
256        return {};
257    },
258    addBreadcrumb: function (crumb) {
259        __sentryQueue.push(function (mod) {
260            try { mod.addBreadcrumb(crumb); } catch (_e) {}
261        });
262    },
263    captureException: function (err, options) {
264        __sentryQueue.push(function (mod) {
265            try { mod.captureException(err, options); } catch (_e) {}
266        });
267        __sentryLoad();
268    },
269    setUser: function (userObjOrNull) {
270        __sentryQueue.push(function (mod) {
271            try { mod.setUser(userObjOrNull); } catch (_e) {}
272        });
273        if (userObjOrNull) {
274            __sentryLoad();
275        }
276    },
277    setContext: function (key, ctx) {
278        __sentryQueue.push(function (mod) {
279            try { mod.setContext(key, ctx); } catch (_e) {}
280        });
281    }
282};
283
284if (typeof window !== 'undefined') {
285    window.Sentry = Sentry;
286}
287
288Sentry.init({
289    dsn: "https://eb92d8c5be66047bf0250a811fb742cd@o4507899926740992.ingest.us.sentry.io/4509911205871616",
290    integrations: [
291        Sentry.browserTracingIntegration(),
292        Sentry.replayIntegration()
293    ],
294    tracesSampleRate: 0.1,
295    replaysSessionSampleRate: 0.01,
296    replaysOnErrorSampleRate: 0.1
297});
298
299if (!window.UniversalModal) {
300    class UniversalModal {
301        constructor(element, options = {}) {
302            this.element = typeof element === 'string' ? document.querySelector(element) : element;
303            this.options = {
304                backdrop: options.backdrop !== undefined ? options.backdrop : true,
305                keyboard: options.keyboard !== undefined ? options.keyboard : true,
306                focus: options.focus !== undefined ? options.focus : true,
307                show: options.show !== undefined ? options.show : false
308            };
309            
310            this.isShown = false;
311            this.backdrop = null;
312            this.scrollbarWidth = 0;
313            
314            this._init();
315            
316            if (this.options.show) {
317                this.show();
318            }
319        }
320        
321        _init() {
322            const closeButtons = this.element.querySelectorAll('[data-dismiss="universal-modal"]');
323            closeButtons.forEach(btn => {
324                btn.addEventListener('click', (e) => {
325                    e.preventDefault();
326                    this.hide();
327                });
328            });
329            
330            if (this.options.backdrop) {
331                this.element.addEventListener('click', (e) => {
332                    if (e.target === this.element) {
333                        this.hide();
334                    }
335                });
336            }
337            
338            if (this.options.keyboard) {
339                document.addEventListener('keydown', (e) => {
340                    if (e.key === 'Escape' && this.isShown) {
341                        this.hide();
342                    }
343                });
344            }
345        }
346        
347        show() {
348            if (this.isShown) return;
349            
350            this.isShown = true;
351            
352            this.scrollbarWidth = this._getScrollbarWidth();
353            
354            if (this.scrollbarWidth > 0) {
355                document.body.style.paddingRight = this.scrollbarWidth + 'px';
356            }
357            
358            document.body.classList.add('universal-modal-open');
359            
360            if (this.options.backdrop) {
361                this._showBackdrop();
362            }
363            
364            this.element.style.display = 'block';
365            this.element.setAttribute('aria-hidden', 'false');
366            
367            setTimeout(() => {
368                this.element.classList.add('show');
369                
370                if (this.options.focus) {
371                    this.element.focus();
372                }
373            }, 10);
374            
375            this._triggerEvent('show');
376        }
377        
378        hide() {
379            if (!this.isShown) return;
380            
381            this.isShown = false;
382            
383            this.element.classList.remove('show');
384            
385            setTimeout(() => {
386                this.element.style.display = 'none';
387                this.element.setAttribute('aria-hidden', 'true');
388                
389                if (this.backdrop) {
390                    this._hideBackdrop();
391                }
392                
393                document.body.classList.remove('universal-modal-open');
394                document.body.style.paddingRight = '';
395                
396                this._triggerEvent('hide');
397            }, 300);
398        }
399        
400        toggle() {
401            this.isShown ? this.hide() : this.show();
402        }
403        
404        _showBackdrop() {
405            this.backdrop = document.createElement('div');
406            this.backdrop.className = 'universal-modal-backdrop fade';
407            document.body.appendChild(this.backdrop);
408            
409            setTimeout(() => {
410                this.backdrop.classList.add('show');
411            }, 10);
412        }
413        
414        _hideBackdrop() {
415            if (!this.backdrop) return;
416            
417            this.backdrop.classList.remove('show');
418            
419            setTimeout(() => {
420                if (this.backdrop && this.backdrop.parentNode) {
421                    this.backdrop.parentNode.removeChild(this.backdrop);
422                }
423                this.backdrop = null;
424            }, 300);
425        }
426        
427        _getScrollbarWidth() {
428            const documentWidth = document.documentElement.clientWidth;
429            return Math.abs(window.innerWidth - documentWidth);
430        }
431        
432        _triggerEvent(eventName) {
433            const event = new CustomEvent('universalModal.' + eventName, {
434                bubbles: true,
435                cancelable: true,
436                detail: { universalModal: this }
437            });
438            this.element.dispatchEvent(event);
439        }
440        
441        static _jQueryInterface(config) {
442            return this.each(function() {
443                let data = this._universalModalInstance;
444                const _config = typeof config === 'object' && config;
445                
446                if (!data) {
447                    data = new UniversalModal(this, _config);
448                    this._universalModalInstance = data;
449                }
450                
451                if (typeof config === 'string') {
452                    if (typeof data[config] === 'undefined') {
453                        throw new TypeError(`No method named "${config}"`);
454                    }
455                    data[config]();
456                }
457            });
458        }
459    }
460
461    document.addEventListener('click', function(e) {
462        const trigger = e.target.closest('[data-toggle="universal-modal"]');
463        if (!trigger) return;
464        
465        e.preventDefault();
466        
467        const targetSelector = trigger.getAttribute('data-target') || trigger.getAttribute('href');
468        if (!targetSelector) return;
469        
470        const targetElement = document.querySelector(targetSelector);
471        if (!targetElement) return;
472        
473        let universalModal = targetElement._universalModalInstance;
474        if (!universalModal) {
475            universalModal = new UniversalModal(targetElement);
476            targetElement._universalModalInstance = universalModal;
477        }
478        
479        universalModal.show();
480    });
481
482    window.UniversalModal = UniversalModal;
483}
484
485const COUNTRY_COOKIE = "countryData";
486
487function getCountryCode() {
488    return new Promise(function(resolve) {
489        var cached = getCookie(COUNTRY_COOKIE);
490        if (cached) {
491            try {
492                var data = JSON.parse(cached);
493                resolve(data.country_code);
494                return;
495            } catch(_) {}
496        }
497
498        fetch('https://www.cloudflare.com/cdn-cgi/trace')
499            .then(function(response) { return response.text(); })
500            .then(function(text) {
501                var data = {};
502                text.split('\n').forEach(function(line) {
503                    var parts = line.split('=');
504                    var key = parts[0];
505                    var value = parts[1];
506                    if (key && value) data[key] = value;
507                });
508                var countryCode = data.loc;
509
510                fetch('https://' + varValues.cdn + '/js/countries.json')
511                    .then(function(resp) { return resp.json(); })
512                    .then(function(countries) {
513                        var country = countries.find(function(c) { return c.code === countryCode; });
514                        var countryName = country ? country.name : "Unknown Country";
515                        var countryData = { country_name: countryName, country_code: countryCode };
516                        setCookie(COUNTRY_COOKIE, JSON.stringify(countryData), 1);
517                        resolve(countryCode);
518                    })
519                    .catch(function() {
520                        var countryData = { country_name: "Unknown", country_code: countryCode };
521                        setCookie(COUNTRY_COOKIE, JSON.stringify(countryData), 1);
522                        resolve(countryCode);
523                    });
524            })
525            .catch(function(error) {
526                resolve(null);
527            });
528    });
529}
530
531function isAgeVerified() {
532    try {
533        return localStorage.getItem('az_age_verified') === '1' || getCookie('agego_aver') === 'yes';
534    } catch (e) {
535        return false;
536    }
537}
538
539async function isAgeVerifiedInDB(uid) {
540    if (!uid) return false;
541    try {
542        const {getDatabase, ref, get} = await import('https://www.gstatic.com/firebasejs/10.5.0/firebase-database.js');
543        const db = getDatabase(app);
544        const snapshot = await get(ref(db, `users/${uid}/agego/verified`));
545        return !!snapshot.val();
546    } catch (e) {
547        return false;
548    }
549}
550
551$(function () {
552    $('.input').on('click', () => {
553        $('.alert-warning').hide();
554    });
555
556    $('input').on('change', () => {
557        $('#newcelebmessage').hide();
558        $('#newmoviemessage').hide();
559        $('#alert-message').hide();
560    });
561});
562
563// ---- viewing history ------------------------------------------------------
564//
565// Stories also remember their permanent id. history-stories is the list of primary
566// keys the history page reads, and the DMCA rename replaces a story's primary key:
567// an entry saved before a rename resolves to nothing after it. storyid (MySQL
568// stories.storyid) never changes, and the page prints it in the rating widget as
569// data-urid="S-M<storyid>". So "primary key:storyid" pairs are kept beside the list
570// for the history page to resolve by (src/utils/historyStore.js reads them).
571//
572// The list itself is untouched — same key, same format — so every reader of it keeps
573// working, and entries saved before this have no pair and resolve by primary key as
574// they always did.
575const STORY_HISTORY_IDS = 'history-stories-sid';
576
577// The same rule as new-story.js getStoryId. "S-M" is the rating widget's prefix,
578// not the story's (a movie's widget prints S-M<movieid>), so the widget is matched
579// to this page by its data-title; without a titled match, only a page with a
580// single widget is trusted. The character set is the one firestore.rules and
581// /fetch-for-history accept; anything else, including what a template prints for
582// a missing value, counts as no storyid.
583function pageStoryId() {
584    const title = String($('meta[name="data:title"]').attr('content') || '');
585    // Every [data-urid], filtered here rather than by an attribute selector: the
586    // Rating-Widget script lowercases data-urid as it initialises, and both page
587    // scripts are ES modules, so they always run after it (AZ-163). A case-sensitive
588    // '[data-urid^="S-M"]' therefore matches nothing in a real browser. The id keeps
589    // whatever case the DOM holds; /fetch-for-history resolves it either way and
590    // answers with MySQL's own spelling.
591    const widgets = $('[data-urid]').toArray().filter(w => /^s-m/i.test(String($(w).attr('data-urid') || '')));
592    const titled = title ? widgets.filter(w => $(w).attr('data-title') === title) : [];
593    const chosen = titled.length ? titled : (widgets.length === 1 ? widgets : []);
594    const ids = new Set(chosen.map(w => String($(w).attr('data-urid') || '').slice(3)));
595    if (ids.size !== 1) return null;
596    const id = [...ids][0];
597    return /^[A-Za-z0-9_=]{1,255}$/.test(id) && !/^(none|null|undefined|nan)$/i.test(id) ? id : null;
598}
599
600// One pair per primary key still in the list. A key cleared from history — on the
601// history page, or by new-main.js's "also clear existing history" — loses its pair
602// the next time a story is recorded.
603function recordStoryId(id, idArray) {
604    const storyid = pageStoryId();
605    if (!storyid) return;
606    const listed = new Set(idArray);
607    const pairs = (localStorage.getItem(STORY_HISTORY_IDS) || '').split(',').filter(p => {
608        const key = p.split(':')[0];
609        return key && key !== id && listed.has(key);
610    });
611    pairs.push(`${id}:${storyid}`);
612    // A revisit of a story already paired changes nothing; skip the write, and the
613    // storage event it would send every other open tab.
614    const next = pairs.join(',');
615    if (next !== (localStorage.getItem(STORY_HISTORY_IDS) || '')) localStorage.setItem(STORY_HISTORY_IDS, next);
616}
617
618function addUserHistory() {
619    try {
620        // The pairs follow the list: once history-stories is gone — cleared on the
621        // history page, or by new-main.js's "also clear existing history" — so are they.
622        if (!localStorage.getItem('history-stories')) localStorage.removeItem(STORY_HISTORY_IDS);
623
624        const contentId = $('meta[name="content_id"]').attr("content");
625        if(!contentId) return;
626        const [type, id] = contentId.split("_");
627        const historyEnabled = localStorage.getItem("history-enabled");
628
629        if (!id || historyEnabled === "false") return;
630
631        const existingIds = localStorage.getItem(`history-${type}`);
632        const idArray = existingIds ? existingIds.split(',') : [];
633
634        // Before the early return, so a story first visited before this shipped gets
635        // its pair on the next visit.
636        if (type === 'stories') {
637            try { recordStoryId(id, idArray.concat(id)); } catch (e) {
638                try { Sentry.captureException(e, { tags: { area: 'history', op: 'recordStoryId' } }); } catch (_e) {}
639            }
640        }
641
642        if (idArray.includes(id)) return;
643
644        idArray.push(id);
645
646        localStorage.setItem(`history-${type}`, idArray.join(','));
647    } catch (e) {
648        try { Sentry.captureException(e, { tags: { area: 'history', op: 'addUserHistory' } }); } catch (_e) {}
649    }
650}
651
652addUserHistory();
653
654let historyEnabled = localStorage.getItem("history-enabled") || "true";
655if(historyEnabled == "true") {
656    $('.account-dropdown__history-toggler__input').prop('checked', true);
657}
658
659// Sexuality-based override: only apply on azfans and aznudefeet.
660// On aznude (gender: "f") and azmen (gender: "m"), the site's own gender is authoritative
661// and the user's stored sexuality preference must NOT override it.
662if (varValues.gender === 'fans' || varValues.gender === 'feet') {
663    if(localStorage.getItem('sexuality') === "Gay") {
664        varValues.genderLong = "male";
665        varValues.liveSexDropdownGenderTier = 'men';
666        varValues.liveSexDropdownCategories = [
667            { title: "Guys", gender: "male", tags: "men" },
668            { title: "Bisexual", gender: "male", tags: "men/bisexuals" },
669            { title: "Gay", gender: "male", tags: "men/gays" },
670            { title: "Straight", gender: "male", tags: "men/straight" },
671            { title: "Twink", gender: "male", tags: "men/twinks" },
672            { title: "Anal", gender: "male", tags: "men/anal" },
673            { title: "Mobile", gender: "male", tags: "men/mobile" },
674            { title: "VR Cams", gender: "male", tags: "men/vr" }
675        ];
676        varValues.aznudeLiveCamOutUrl = 'men';
677    }
678
679    if(localStorage.getItem('sexuality') === "Trans") {
680        varValues.genderLong = "trans";
681        varValues.liveSexDropdownGenderTier = 'trans';
682        varValues.liveSexDropdownCategories = [
683            { title: "Trans", gender: "trans", tags: "trans" },
684            { title: "Teens", gender: "trans", tags: "trans/teens" },
685            { title: "Big Tits", gender: "trans", tags: "trans/big-tits" },
686            { title: "Big Ass", gender: "trans", tags: "trans/big-ass" },
687            { title: "Anal", gender: "trans", tags: "trans/anal" },
688            { title: "Blowjob", gender: "trans", tags: "trans/blowjob" },
689            { title: "VR Cams", gender: "trans", tags: "trans/vr" }
690        ];
691        varValues.aznudeLiveCamOutUrl = 'trans';
692    }
693}
694
695if (varValues.gender === 'feet') {
696    const feetSexuality = localStorage.getItem('sexuality');
697    if (feetSexuality === 'Gay') {
698        varValues.liveSexDropdownGenderTier = 'men/foot-fetish';
699        varValues.liveSexDropdownGenderTags = 'men/foot-fetish';
700        varValues.liveSexDropdownCategories = [
701            { title: "Guys Feet", gender: "male", tags: "men/foot-fetish" },
702            { title: "Guys", gender: "male", tags: "men" },
703            { title: "Bisexual", gender: "male", tags: "men/bisexuals" },
704            { title: "Gay", gender: "male", tags: "men/gays" },
705            { title: "Straight", gender: "male", tags: "men/straight" },
706            { title: "Twink", gender: "male", tags: "men/twinks" },
707            { title: "Anal", gender: "male", tags: "men/anal" },
708            { title: "Mobile", gender: "male", tags: "men/mobile" },
709            { title: "VR Cams", gender: "male", tags: "men/vr" }
710        ];
711    } else {
712        varValues.liveSexDropdownGenderTier = 'girls/foot-fetish';
713        varValues.liveSexDropdownGenderTags = 'girls/foot-fetish';
714        varValues.liveSexDropdownCategories = [
715            { title: "Girls Feet", gender: "female", tags: "girls/foot-fetish" },
716            { title: "Girls", gender: "female", tags: "girls" },
717            { title: "Couples", gender: "maleFemale", tags: "couples" },
718            { title: "Best for private", gender: "female", tags: "girls/best" },
719            { title: "Interactive Toys", gender: "female", tags: "girls/interactive-toys" },
720            { title: "Mobile", gender: "female", tags: "girls/mobile" },
721            { title: "VR Cams", gender: "female", tags: "girls/vr" }
722        ];
723    }
724}
725
726function resolveGenderForWorker() {
727    if (varValues.genderLong === 'feet') {
728        return localStorage.getItem('sexuality') === 'Gay' ? 'male' : 'female';
729    }
730    return varValues.genderLong;
731}
732
733let __azRestoreAttempted = false;
734
735// How long a "there is no shared session" answer is trusted before asking again.
736//
737// The in-memory guard below only covers one page load, so a logged-out visitor
738// used to fire one check-session per page view - and for them it can only ever
739// return 401. That is the bulk of the 4xx traffic this endpoint serves.
740//
741// A discarded draft of this cached the negative in sessionStorage, which held it
742// for the entire tab session - a login on another domain would never have been
743// picked up until the tab closed. It never shipped, but the shape of that mistake
744// is why this window is bounded and expires on its own.
745//
746// 2 minutes, from measured per-visitor gaps between session checks rather than
747// intuition (1,100 gaps over 6 hours, pre-filter logs). The elimination curve is
748// very steep and then flat: 60s removes 90% of the repeat traffic, 120s removes
749// 94%, and 10 minutes only reaches 97% - median gap between checks is under 30s.
750// So nearly all the saving is banked inside the first minute, while the cost
751// grows linearly with the window: a sibling domain browsed logged-out inside it
752// keeps showing a logged-out navbar until this expires, even though you have
753// since signed in elsewhere. Going 120s -> 10min would buy ~3 more points of
754// saving for 8 more minutes of that. This is the knee, not a round number.
755const AZ_NO_SESSION_TTL_MS = 2 * 60 * 1000;
756const AZ_NO_SESSION_KEY = 'az_no_session_until';
757
758function azNoSessionCached() {
759    try {
760        const until = parseInt(localStorage.getItem(AZ_NO_SESSION_KEY) || '0', 10);
761        // Upper bound as well as lower: a value further out than one full TTL
762        // cannot have been written by this code against a correct clock, so treat
763        // it as garbage rather than honouring it. Without this, a clock that was
764        // skewed forward when the key was written keeps the cache alive for the
765        // length of the skew after the clock is corrected.
766        const now = Date.now();
767        return Number.isFinite(until) && now < until && until <= now + AZ_NO_SESSION_TTL_MS;
768    } catch (_e) {
769        return false; // storage blocked (Safari private mode) - just ask
770    }
771}
772
773function azRememberNoSession() {
774    try {
775        localStorage.setItem(AZ_NO_SESSION_KEY, String(Date.now() + AZ_NO_SESSION_TTL_MS));
776    } catch (_e) {}
777}
778
779function azForgetNoSession() {
780    try { localStorage.removeItem(AZ_NO_SESSION_KEY); } catch (_e) {}
781}
782
783// A 5xx or a dropped request is not an answer, so it must not be written to
784// az_no_session_until - the comment at the check-session call below explains
785// why. But "not an answer" is no reason to ask again on the very next page
786// view either. Those failures are correlated across every visitor at once, so
787// the endpoint gets its normal load plus the immediate retries of everyone it
788// has just failed: on 22 Aug, 4,526 of the 8,672 requests Cloud Run refused in
789// two minutes were check-session.
790//
791// Deliberately a separate key from az_no_session_until. That one means "there
792// is no shared session"; this one means "we could not ask, wait a moment".
793// Collapsing them would suppress a legitimate restore for a full two minutes on
794// the strength of one failed request, which is the mistake that comment guards
795// against.
796//
797// Short, because the only job is to break up the synchronised wave - long
798// enough that clicking through pages during an outage does not fire one call
799// per page, short enough not to delay recovery. The jitter matters more than
800// the number: without it every visitor's retry lands in the same second, which
801// is the same herd wearing a different hat.
802const AZ_RETRY_AFTER_KEY = 'az_session_retry_after';
803const AZ_RETRY_BASE_MS   = 5 * 1000;
804const AZ_RETRY_JITTER_MS = 5 * 1000;
805
806function azAskTooSoon() {
807    try {
808        const until = parseInt(localStorage.getItem(AZ_RETRY_AFTER_KEY) || '0', 10);
809        // Upper bound for the same reason as azNoSessionCached: a value further
810        // out than one full window cannot have been written by this code against
811        // a correct clock, so treat it as garbage rather than honouring it.
812        const now = Date.now();
813        return Number.isFinite(until) && now < until
814            && until <= now + AZ_RETRY_BASE_MS + AZ_RETRY_JITTER_MS;
815    } catch (_e) {
816        return false; // storage blocked (Safari private mode) - just ask
817    }
818}
819
820function azBackOff() {
821    try {
822        const wait = AZ_RETRY_BASE_MS + Math.floor(Math.random() * AZ_RETRY_JITTER_MS);
823        localStorage.setItem(AZ_RETRY_AFTER_KEY, String(Date.now() + wait));
824    } catch (_e) {}
825}
826
827function azClearBackOff() {
828    try { localStorage.removeItem(AZ_RETRY_AFTER_KEY); } catch (_e) {}
829}
830
831export async function azTryRestoreSession() {
832    // Attempt at most once per page load.
833    if (__azRestoreAttempted) return false;
834    __azRestoreAttempted = true;
835
836    // Skip the round trip when we already know there is no shared session. This
837    // is the whole point of the change: for an anonymous visitor the answer is
838    // always the same, so asking once per page view is one request per page for
839    // information we already have.
840    if (azNoSessionCached()) return false;
841
842    // The endpoint failed us recently. Asking again straight away is what turns
843    // one bad minute into a retry storm.
844    if (azAskTooSoon()) return false;
845
846    try {
847        const res = await fetch(`${PRIMARY_BASE}/auth/check-session`, {
848            method: 'GET',
849            credentials: 'include',
850        });
851
852        if (!res.ok) {
853            // Only a 401 is an answer. A 5xx - Redis down behind the endpoint, or
854            // a 502/503 from the edge - means we failed to ask, exactly like the
855            // fetch rejection below, and caching it would suppress a legitimate
856            // restore for a full window. Those failures are correlated across
857            // users, so a few seconds of backend trouble would otherwise suppress
858            // restores for everyone who loaded a page during it.
859            if (res.status === 401) {
860                azRememberNoSession();
861                azClearBackOff(); // a 401 is an answer, so the endpoint is healthy
862            } else {
863                azBackOff();
864            }
865            return false;
866        }
867
868        azClearBackOff(); // it answered, whatever the answer turns out to be
869
870        const data = await res.json();
871        if (!data || !data.custom_token) { azRememberNoSession(); return false; } // 200, no session
872
873        await signInWithCustomToken(auth, data.custom_token);
874        azForgetNoSession();
875        // Cookie was readable here, so this browser supports the shared session.
876        // Arm cross-domain logout for the logged-in sync path.
877        try {
878            localStorage.setItem('az_cookie_backed', '1');
879        } catch (_e) {}
880        return true; // re-fires onAuthStateChanged with the user
881    } catch (e) {
882        // Deliberately not cached. A network failure means we could not ask, not
883        // that there is no session - remembering it would suppress a legitimate
884        // restore for a full window on the strength of one dropped request. It
885        // does earn a short backoff, which is a different thing: wait a moment,
886        // then ask again.
887        azBackOff();
888        console.warn('[CrossDomainAuth] restore failed:', e);
889        return false;
890    }
891}
892
893let __azSessionSynced = false;
894
895// For a logged-in user: keep this domain in sync with the shared session.
896// If the cookie is readable, arm cross-domain logout. If the cookie is gone and we
897// previously confirmed it worked here, sign out so logout propagates across domains.
898// The armed gate means cookie-blocked browsers (Safari) never false-sign-out.
899export async function azSyncSession() {
900    if (__azSessionSynced) return false;
901    __azSessionSynced = true;
902
903    const p = window.location.pathname;
904    if (p === '/signin.html' || p === '/signup.html') return false; // active login, do not race it
905
906    // Same backoff as the restore path. Returning false here leaves the visitor
907    // signed in, which is the safe direction to fail.
908    if (azAskTooSoon()) return false;
909
910    let armed = false;
911    try { armed = localStorage.getItem('az_cookie_backed') === '1'; } catch (_e) {}
912
913    try {
914        const res = await fetch(`${PRIMARY_BASE}/auth/check-session`, {
915            method: 'GET',
916            credentials: 'include',
917        });
918
919        if (res.ok) {
920            azClearBackOff();
921            try { localStorage.setItem('az_cookie_backed', '1'); } catch (_e) {}
922            return false; // session still alive
923        }
924
925        // Only a 401 is an answer, exactly as in azTryRestoreSession. Anything
926        // else - a 5xx from the backend, a 502/503 from the edge - means we
927        // failed to ask. This branch did not exist before, so any non-ok reply
928        // was read as "the shared session is gone" and signed the visitor out:
929        // a few seconds of backend trouble logged out everyone whose cookie had
930        // ever worked on this domain, and the resulting onAuthStateChanged(null)
931        // sent each of them straight back to check-session a second time.
932        if (res.status !== 401) {
933            azBackOff();
934            return false;
935        }
936
937        azClearBackOff(); // a 401 is an answer, so the endpoint is healthy
938
939        if (!armed) return false; // never confirmed the cookie works here; do not risk a false logout
940
941        // Shared session is gone (logout on another domain). Propagate it here.
942        try { localStorage.removeItem('az_cookie_backed'); } catch (_e) {}
943        await signOut(auth);
944        return true; // re-fires onAuthStateChanged with null
945    } catch (e) {
946        azBackOff();
947        console.warn('[CrossDomainAuth] session sync failed:', e);
948        return false;
949    }
950}
951
952$(function () {
953    onAuthStateChanged(auth, async (user) => {
954        if (user) {
955            // Somebody is signed in here, so any remembered "no shared session"
956            // is stale. Clearing it on every signed-in render covers every route
957            // in on this origin - local sign-in, restore, a token refresh -
958            // without each of them needing to know about the cache. It does not
959            // reach the sibling domains; their own copies expire on their own.
960            azForgetNoSession();
961
962            if (await azSyncSession()) return; // shared session gone -> signed out here too
963
964            if (!user.emailVerified) {
965                $('body').prepend(`
966                    <div class="email-verification-bar">
967                        Please verify your email (${user.email}). Check your inbox or spam folder, or <a href="#" id="resend-link">click here</a> to resend. You can also go to <a href="/profile.html#settings">Settings</a> to change your email.
968                        <div id="email-verification-response" style="display: none;"></div>
969                    </div>
970                `);
971            }
972
973            $('#resend-link').on('click', async function(e) {
974                e.preventDefault();
975                const responseSpan = $('#email-verification-response');
976            
977                try {
978                    await sendEmailVerification(auth.currentUser);
979                    responseSpan.text('Verification email has been resent.').removeClass('error').addClass('success').show();
980                } catch (error) {
981                    try { Sentry.captureException(error, { tags: { area: 'auth', op: 'sendEmailVerification' } }); } catch (_e) {}
982                    if (error.code === "auth/too-many-requests") {
983                        responseSpan.text('Too many requests. Please try again later.').removeClass('success').addClass('error').show();
984                    } else {
985                        responseSpan.text('Error sending verification email: ' + error.message).removeClass('success').addClass('error').show();
986                    }
987                } finally {
988                    setTimeout(() => {
989                        responseSpan.hide();
990                    }, 30000);
991                }
992    
993                responseSpan.on('click', function(){
994                    responseSpan.hide();
995                })
996            });
997
998            try {
999                const token = await user.getIdTokenResult(true);
1000                Sentry.setUser({
1001                    id: user.uid || undefined,
1002                    email: user.email || undefined
1003                });
1004                Sentry.setContext('firebaseClaims', token?.claims || {});
1005
1006                const hasLegacyCookie = document.cookie.split('; ').some(cookie => cookie.startsWith('az_legacy_user='));
1007
1008                if (!hasLegacyCookie) {
1009                    const creationTime = new Date(user.metadata.creationTime);
1010                    const cutoffDate = new Date('2026-01-01T00:00:00Z');
1011                    
1012                    const cookieValue = creationTime < cutoffDate ? '1' : '0';
1013                    document.cookie = `az_legacy_user=${cookieValue}; path=/; max-age=86400; SameSite=Strict; Secure`;
1014
1015                    getCountryCode().then(function(countryCode) {
1016                        var isGatedCountry = countryCode === 'GB' || countryCode === 'AU';
1017                        if (isGatedCountry) return window.location.reload();
1018                    });
1019                }
1020            } catch (e) {
1021                try { Sentry.captureException(e, { tags: { area: 'auth', op: 'getIdTokenResult' } }); } catch (_e) {}
1022            }
1023
1024            $navbar.removeClass("is-not-logged-in");
1025            $navbar.addClass("is-logged-in");
1026
1027            $('.logoutbtn').on('click', async (e) => {
1028                e.preventDefault();
1029                localStorage.removeItem('azn_id');
1030                localStorage.removeItem('az_cookie_backed');
1031                document.cookie = `az_legacy_user=; path=/; max-age=0`;
1032                await signOut(auth);
1033                try {
1034                    await fetch(`${PRIMARY_BASE}
1034/auth/delete-session`, { method: 'POST', credentials: 'include', keepalive: true });
1035                } catch (_e) {}
1036                const AUTH_GATED_REACT_PAGES = new Set(['profile','favorites','upload','view-uploads','edit-upload','edit-report','manage-content','manageuser','two-factor-authentication']);
1037                const pageType = document.getElementById('react-root')?.getAttribute('data-page');
1038                window.location.href = (pageType && AUTH_GATED_REACT_PAGES.has(pageType)) ? '/' : window.location.href;
1039            });
1040
1041            if ($('.ribbon-desktop-live-1').length) {
1042                $('.ribbon-desktop-live-1').attr('href', `/aznudelive.html${constructAZNLiveNavbarLink($('.ribbon-desktop-live-1').attr('href'))}`);
1043                $('.ribbon-desktop-live-1').removeAttr('target');
1044                $('.ribbon-desktop-live-1').removeAttr('rel');
1045            }
1046        
1047            if ($('.ribbon-desktop-live-2').length) {
1048                $('.ribbon-desktop-live-2').attr('href', `/aznudelive.html${constructAZNLiveNavbarLink($('.ribbon-desktop-live-2').attr('href'))}`);
1049                $('.ribbon-desktop-live-2').removeAttr('target');
1050                $('.ribbon-desktop-live-2').removeAttr('rel');
1051            }
1052        
1053            if ($('.ribbon-mobile-live').length) {
1054                $('.ribbon-mobile-live').attr('href', `/aznudelive.html${constructAZNLiveNavbarLink($('.ribbon-mobile-live').attr('href'))}`);
1055                $('.ribbon-mobile-live').removeAttr('target');
1056                $('.ribbon-mobile-live').removeAttr('rel');
1057            }
1058        } else {
1059            Sentry.setUser(null);
1060
1061            // Cross-domain restore: Firebase has authoritatively reported no local
1062            // session, so this is the right point to try the centralized cookie.
1063            const restored = await azTryRestoreSession();
1064            if (restored) return; // re-fire handles the logged-in render
1065        }
1066
1067        const baseUrl = `/cam-out.html?userId=e5832eb0a24a6150f7d3967ae12ea6e95a0e3ce83557ea66397b6ed5ba72751e&targetDomain=aznudelive.com/${varValues.aznudeLiveCamOutUrl}&p=verify50af`;
1068        const ulElement = document.querySelector('.live-dropdown__ul');
1069
1070        if(ulElement){
1071            varValues.liveSexDropdownCategories.forEach((category, index) => {
1072                const liElement = document.createElement('li');
1073                liElement.className = 'live-dropdown__li';
1074                if (index === 0) liElement.classList.add('active');
1075                
1076                const aElement = document.createElement('a');
1077                aElement.href = '#';
1078                aElement.setAttribute('data-gender', category.gender);
1079                aElement.setAttribute('data-tag', category.tags);
1080                aElement.textContent = category.title;
1081    
1082                liElement.appendChild(aElement);
1083                ulElement.appendChild(liElement);
1084    
1085                aElement.addEventListener('click', function(event) {
1086                    event.preventDefault();
1087                    event.stopPropagation();
1088    
1089                    document.querySelectorAll('.live-dropdown__li').forEach(li => li.classList.remove('active'));
1090                    liElement.classList.add('active');
1091    
1092                    const gridList = document.querySelector('.live-dropdown__promotions-grid');
1093                    gridList.innerHTML = '';
1094    
1095                    const gender = aElement.getAttribute('data-gender');
1096                    const tag = aElement.getAttribute('data-tag');
1097                    setupLiveDropdown(gender, tag);
1098                });
1099            });
1100        }
1101
1102        const gridList = document.querySelector('.live-dropdown__promotions-grid');
1103        let hlsInstances = {};
1104
1105        const generateSingleItem = (model, gender) => {
1106            try {
1107                let icon;
1108
1109                switch (model.gender) {
1110                    case 'male':
1111                        icon = `
1112                        <svg xmlns="http://www.w3.org/2000/svg" fill="#5AC1F2" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 511.01"><path fill-rule="nonzero" d="m456.72 96.62-115.49 115.5c22.46 31.03 35.72 69.17 35.72 110.41 0 52.04-21.1 99.17-55.2 133.27-34.11 34.1-81.23 55.21-133.28 55.21-52.03 0-99.17-21.11-133.27-55.21C21.1 421.7 0 374.57 0 322.53c0-52.04 21.1-99.17 55.2-133.27 34.1-34.1 81.23-55.21 133.27-55.21 42.91 0 82.47 14.35 114.16 38.5L419.89 55.28h-62.84V0H512v158.91h-55.28V96.62zM282.66 228.35c-24.1-24.1-57.41-39.02-94.19-39.02s-70.08 14.92-94.18 39.02c-24.1 24.1-39.01 57.4-39.01 94.18 0 36.78 14.91 70.09 39.01 94.19 24.1 24.1 57.4 39.01 94.18 39.01 36.78 0 70.09-14.91 94.19-39.01 24.1-24.1 39.01-57.41 39.01-94.19s-14.91-70.08-39.01-94.18z"/>
1112</svg>`;
1113                        break;
1114
1115                    case 'female':
1116                        icon = `
1117                        <svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 361 511.42"><path fill="#F576AB" fill-rule="nonzero" d="M203.64 359.53v44.17h78.58v52.94h-78.58v54.78H150.7v-54.78H72.13V403.7h78.57v-45.15c-37.91-6.3-71.82-24.41-97.83-50.42C20.21 275.47 0 230.35 0 180.5c0-49.84 20.21-94.97 52.87-127.63S130.65 0 180.5 0c49.84 0 94.97 20.21 127.63 52.87S361 130.66 361 180.5c0 49.84-20.21 94.97-52.87 127.63-27.52 27.52-63.9 46.2-104.49 51.4zM270.7 90.3c-23.08-23.08-54.98-37.36-90.2-37.36-35.23 0-67.12 14.28-90.2 37.36s-37.36 54.98-37.36 90.2c0 35.23 14.28 67.12 37.36 90.2s54.97 37.36 90.2 37.36c35.22 0 67.12-14.28 90.2-37.36s37.36-54.97 37.36-90.2c0-35.22-14.28-67.12-37.36-90.2z"/></svg>`;
1118                        break;
1119
1120                    case 'males':
1121                        icon = `
1122                        <svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 490.08"><path fill="#5AC1F2" fill-rule="nonzero" d="M347.89 73.6l-87.97 87.97a143.238 143.238 0 0120.99 42.2c26.75 2.84 51.3 13.03 71.6 28.51l89.33-89.33h-47.87v-42.11H512v121.05h-42.11v-47.45l-87.97 87.97c17.11 23.64 27.21 52.7 27.21 84.11 0 39.64-16.08 75.54-42.05 101.51-25.98 25.98-61.87 42.05-101.52 42.05-39.63 0-75.54-16.07-101.51-42.05-16.42-16.42-28.89-36.81-35.83-59.6-33.51-3.57-63.59-18.66-86.17-41.24C16.07 321.22 0 285.32 0 245.68c0-39.65 16.07-75.54 42.05-101.52 25.97-25.97 61.87-42.05 101.51-42.05 32.69 0 62.82 10.93 86.96 29.32l89.32-89.32h-47.87V0H390v121.05h-42.11V73.6zm-60.77 173.76c-.44 38.97-16.43 74.23-42.04 99.83-19.54 19.54-44.7 33.48-72.84 39.19 5.12 11.99 12.5 22.8 21.58 31.88 18.36 18.36 43.73 29.72 71.74 29.72 28.02 0 53.39-11.36 71.75-29.72 18.35-18.35 29.71-43.72 29.71-71.74 0-28.02-11.36-53.39-29.71-71.74-13.51-13.51-30.83-23.23-50.19-27.42zm-165.11 97.48c.45-38.98 16.43-74.23 42.04-99.84 19.54-19.54 44.7-33.48 72.84-39.18-5.13-11.99-12.5-22.8-21.58-31.89-18.36-18.35-43.73-29.71-71.75-29.71-28.02 0-53.38 11.36-71.74 29.71-18.36 18.36-29.71 43.73-29.71 71.75 0 28.02 11.35 53.39 29.71 71.74 13.51 13.51 30.82 23.23 50.19 27.42zm123-97.69c-19.77 4.06-37.45 13.88-51.19 27.63-18.03 18.03-29.31 42.84-29.7 70.27 19.77-4.07 37.45-13.89 51.19-27.63 18.04-18.04 29.31-42.84 29.7-70.27z"/></svg>`;
1123                        break;
1124
1125                    case 'females':
1126                        icon = `
1127                        <svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 442.01"><path fill="#F576AB" fill-rule="nonzero" d="M176.01 310.73v38.18h67.9v45.75h-67.9v47.35h-45.76v-47.35H62.34v-45.75h67.91v-39.02c-32.77-5.45-62.08-21.1-84.56-43.58C17.47 238.09 0 199.08 0 156.01c0-43.08 17.47-82.09 45.69-110.32C73.92 17.47 112.92 0 156 0c38.05 0 72.92 13.63 100 36.27C283.07 13.63 317.95 0 356 0c43.08 0 82.08 17.47 110.31 45.69C494.53 73.92 512 112.93 512 156.01c0 43.07-17.47 82.08-45.69 110.3-23.79 23.79-55.23 39.93-90.31 44.42v38.18h67.91v45.75H376v47.35h-45.75v-47.35h-67.91v-45.75h67.91v-39.02c-27.96-4.65-53.42-16.73-74.25-34.15-22.25 18.6-49.77 31.13-79.99 34.99zM286.47 70.44c16.14 24.57 25.54 53.97 25.54 85.57 0 31.58-9.4 60.99-25.54 85.55 18.96 15.44 43.16 24.69 69.53 24.69 30.44 0 58.01-12.34 77.96-32.29 19.94-19.94 32.29-47.51 32.29-77.95 0-30.45-12.35-58.02-32.29-77.96C414.01 58.1 386.44 45.76 356 45.76c-26.37 0-50.57 9.25-69.53 24.68zm-60.94 171.12C209.39 217 200 187.59 200 156.01c0-31.6 9.39-61 25.53-85.57-18.96-15.43-43.17-24.68-69.53-24.68-30.44 0-58.01 12.34-77.95 32.29-19.95 19.94-32.29 47.51-32.29 77.96 0 30.44 12.34 58.01 32.29 77.95 19.94 19.95 47.51 32.29 77.95 32.29 26.36 0 50.56-9.25 69.53-24.69zM256 109.52c-6.57 14.13-10.25 29.88-10.25 46.49 0 16.6 3.68 32.35 10.25 46.48 6.58-14.13 10.25-29.88 10.25-46.48 0-16.61-3.67-32.36-10.25-46.49z"/></svg>`;
1128                        break;
1129
1130                    case 'maleFemale':
1131                        icon = `
1132                        <svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 502.03"><g fill-rule="nonzero"><path fill="#5AC1F2" d="M285.39 93.9c30.15 0 57.95 10.09 80.21 27.06l82.86-82.86h-30.7c-10.52 0-19.05-8.53-19.05-19.05C398.71 8.53 407.24 0 417.76 0h75.19C503.47 0 512 8.53 512 19.05v77.53c0 10.52-8.53 19.05-19.05 19.05-10.52 0-19.05-8.53-19.05-19.05V67.56l-81.2 81.2c15.77 21.79 25.07 48.57 25.07 77.53 0 36.55-14.82 69.64-38.77 93.6-23.96 23.95-57.06 38.77-93.61 38.77-22.38 0-43.46-5.56-61.95-15.36 5.14-3.95 10-8.21 14.55-12.76 5.19-5.19 10-10.77 14.38-16.7 10.27 3.87 21.4 5.99 33.02 5.99 25.84 0 49.23-10.47 66.16-27.4 16.92-16.92 27.4-40.31 27.4-66.14 0-25.84-10.48-49.23-27.4-66.16-16.93-16.93-40.32
1132-27.4-66.16-27.4-25.83 0-49.22 10.47-66.15 27.4-16.93 16.93-27.4 40.32-27.4 66.16 0 13.23 2.75 25.81 7.7 37.22-3.39 5.89-7.58 11.32-12.42 16.16-4.94 4.93-10.48 9.19-16.5 12.62-11.2-19.43-17.6-41.97-17.6-66 0-36.56 14.82-69.65 38.77-93.61 23.95-23.96 57.05-38.78 93.6-38.78z"/><path fill="#F576AB" d="M132.68 92.55c22.48 0 43.65 5.6 62.2 15.46-5.23 4-10.17 8.33-14.8 12.96-5.14 5.13-9.9 10.66-14.25 16.53-10.31-3.9-21.48-6.03-33.15-6.03-25.9 0-49.34 10.5-66.3 27.46-16.97 16.97-27.46 40.41-27.46 66.3 0 25.9 10.49 49.34 27.46 66.31C83.34 308.5 106.79 319 132.68 319s49.34-10.5 66.3-27.47c16.97-16.96 27.46-40.4 27.46-66.3 0-13.32-2.77-25.99-7.79-37.47a77.953 77.953 0 0112.3-15.98 77.07 77.07 0 0116.75-12.72c11.23 19.48 17.66 42.08 17.66 66.17 0 36.64-14.86 69.81-38.87 93.82-19.77 19.77-45.75 33.33-74.76 37.5v33.15h55.18c10.52 0 19.05 8.53 19.05 19.05 0 10.52-8.53 19.05-19.05 19.05h-55.18v55.18c0 10.52-8.53 19.05-19.05 19.05-10.52 0-19.05-8.53-19.05-19.05V427.8H58.45c-10.52 0-19.05-8.53-19.05-19.05 0-10.52 8.53-19.05 19.05-19.05h55.18v-33.15c-29.01-4.17-55-17.73-74.77-37.5C14.85 295.04 0 261.87 0 225.23c0-36.63 14.85-69.8 38.86-93.81 24.01-24.01 57.18-38.87 93.82-38.87z"/></g></svg>`;
1133                        break;
1134                    case 'tranny':
1135                        icon = `
1136                        <svg width="16" height="16" viewBox="0 0 12 12" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="67.535" y1="422.773" x2="412.713" y2="79.813" id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f6b"/><stop offset="1" stop-color="#0c30ad"/></linearGradient><linearGradient xlink:href="#a" id="b" gradientUnits="userSpaceOnUse" x1="6" y1="9.375" x2="10.75" y2="5.75"/></defs><path style="fill:none;stroke:url(#b);stroke-width:.99975;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" d="M4.125 9.375h3.75M3.519.999H1.75v1.77m.669.666 1.768-1.767M6 11V7.75M4.233 3.483l-.62-.621-.622-.62-.62-.622L1.75 1m6.43 0h2.07v2.069M8.5 5.249c0-.689-.28-1.314-.733-1.766A2.5 2.5 0 0 0 6 2.75c-.69 0-1.314.28-1.767.733A2.5 2.5 0 0 0 3.5 5.25c0 1.379 1.121 2.5 2.5 2.5s2.5-1.121 2.5-2.5zm-.733-1.766L10.25.999"/></svg>
1137                        `;
1138                        break;
1139
1140                    default:
1141                        break;
1142                }
1143
1144                const sourceId = `sourceId=aznude-interactive-bar-model-${gender === 'male' ? "men" : "women"}`;
1145                const fallbackUrl = `https://aznudelive.com/${model.username}?${sourceId}&userId=e5832eb0a24a6150f7d3967ae12ea6e95a0e3ce83557ea66397b6ed5ba72751e&targetDomain=aznudelive.com/${varValues.aznudeLiveCamOutUrl}&p=verify50af`;
1146                const href = user ? `${baseUrl}&${sourceId}&site=/${model.username}&fallbackUrl=${encodeURIComponent(fallbackUrl)}` : fallbackUrl;
1147        
1148                return `<li class="live-dropdown__promotions-grid-item">
1149                    <a href="${href}" data-video-src="${model.stream?.url}" target="_blank" class="promotions-grid-item__wrapper">
1150                        <div class="promotions-grid-item__image" style="background-image: url('${model.popularSnapshotUrl}')"></div>
1151                        <h4 class="promotions-grid-item__name"><span>${model.username}</span>${icon}</h4>
1152                    </a>
1153                </li>`
1154            } catch (e) {
1155                try { Sentry.captureException(e, { tags: { area: 'liveDropdown', op: 'generateSingleItem' } }); } catch (_e) {}
1156                return '';
1157            }
1158        };
1159
1160        async function setupLiveDropdown(gender, tag, isLoggedIn) {
1161            try {
1162                const workerGender = gender === 'feet' ? (localStorage.getItem('sexuality') === 'Gay' ? 'male' : 'female') : gender;
1163                const res = await fetch(`https://broad-term-5811.aznude.workers.dev/?gender=${workerGender}&tag=${tag}&limit=10`);
1164                const data = await res.json();
1165        
1166                gridList.innerHTML = '';
1167        
1168                data.models.forEach(model => gridList.innerHTML += generateSingleItem(model, gender));
1169        
1170                const sourceId = `sourceId=aznude-interactive-bar-${varValues.genderLong === 'male' ? "men" : "women"}`;
1171                const fallbackUrl = `https://aznudelive.com/${tag}?${sourceId}userId=e5832eb0a24a6150f7d3967ae12ea6e95a0e3ce83557ea66397b6ed5ba72751e&targetDomain=aznudelive.com/${varValues.aznudeLiveCamOutUrl}&p=verify50af`;
1172                const href = user ? `${baseUrl}&${sourceId}&site=${tag}&fallbackUrl=${encodeURIComponent(fallbackUrl)}` : fallbackUrl;
1173
1174                document.querySelector('.ribbon-desktop-live-1').setAttribute('href', href)
1175                document.querySelector('.live-dropdown__promotions-header__title').setAttribute('href', href);
1176                document.querySelector('.live-dropdown__promotions-header__title').innerHTML = `Chat with <span>Live</span> ${gender === "male" ? "men" : gender === "trans" ? "trans girls" : "girls"} in ${data.country} now!`;
1177                document.querySelector('.live-dropdown__promotions-header__button').setAttribute('href', href);
1178                document.querySelector('.live-dropdown__promotions-header__button').innerHTML = gender === 'male' ? "View Guys >" : gender === "trans" ? "View Trans Girls >" : "View Girls >";
1179
1180                if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
1181                    document.querySelectorAll('.promotions-grid-item__wrapper').forEach(wrapper => {
1182                        wrapper.addEventListener('touchend', function () {
1183                            const video = this.querySelector('video');
1184                            if (video) {
1185                                video.style.transition = 'opacity 0.5s';
1186                                video.style.opacity = 0;
1187                                video.addEventListener('transitionend', () => video.remove());
1188                            }
1189                        });
1190                        wrapper.addEventListener('touchstart', function () {
1191                            const vid_src = this.getAttribute('data-video-src');
1192                            const vid = generateVideo(vid_src);
1193                            const imageDiv = this.querySelector('.promotions-grid-item__image');
1194                            imageDiv.appendChild(vid);
1195                            vid.style.transition = 'opacity 0.5s';
1196                            vid.style.opacity = 0;
1197                            setTimeout(() => vid.style.opacity = 1, 0);
1198                        });
1199                    });
1200                } else {
1201                    document.querySelectorAll('.promotions-grid-item__wrapper').forEach(wrapper => {
1202                        wrapper.addEventListener('mouseenter', function hoverIn() {
1203                            const vid_src = this.getAttribute('data-video-src');
1204                            if (this.getAttribute('data-video-initialized') !== 'true') {
1205                                if (this.hls) {
1206                                    this.hls.destroy();
1207                                }
1208                                this.setAttribute('data-video-initialized', 'true');
1209                                const vid = generateVideo(vid_src);
1210                                this.querySelector('.promotions-grid-item__image').appendChild(vid);
1211                                vid.style.transition = 'opacity 0.5s';
1212                                vid.style.opacity = 0;
1213                                setTimeout(() => vid.style.opacity = 1, 0);
1214                            }
1215                        });
1216
1217                        wrapper.addEventListener('mouseleave', function hoverOut() {
1218                            const video = this.querySelector('video');
1219                            if (video) {
1220                                video.style.transition = 'opacity 0.5s';
1221                                video.style.opacity = 0;
1222                                video.addEventListener('transitionend', () => {
1223                                    this.setAttribute('data-video-initialized', 'false');
1224                                    const hlsInstance = hlsInstances[video.id];
1225                                    if (hlsInstance) {
1226                                        hlsInstance.destroy();
1227                                        delete hlsInstances[video.id];
1228                                    }
1229                                    video.remove();
1230                                });
1231                            }
1232                        });
1233                    });
1234                }
1235            } catch (e) {
1236                try { Sentry.captureException(e, { tags: { area: 'liveDropdown', op: 'setupLiveDropdown' } }); } catch (_e) {}
1237            }
1238        }
1239
1240        function generateVideo(src) {
1241            try {
1242                const videoId = 'video_' + Date.now();
1243                const video = document.createElement('video');
1244                video.id = videoId;
1245                video.autoplay = true;
1246                video.muted = true;
1247                video.loop = true;
1248                video.style.position = 'absolute';
1249                video.style.top = '0px';
1250                video.style.left = '0px';
1251                video.style.border = `solid 2px ${varValues.primaryColor}`;
1252                video.style.objectFit = 'cover';
1253                video.style.width = '100%';
1254                video.style.height = '100%';
1255
1256                const source = document.createElement('source');
1257                source.src = src;
1258                video.appendChild(source);
1259
1260                const nativeVideo = video;
1261                if (Hls.isSupported()) {
1262                    const hls = new Hls({
1263                        fragLoadingTimeOut: 2000,
1264                        fragLoadingMaxRetry: 2,
1265                        fragLoadingRetryDelay: 0
1266                    });
1267                    hls.loadSource(src);
1268                    hls.attachMedia(nativeVideo);
1269                    hlsInstances[videoId] = hls;
1270                } else {
1271                    video.src = src;
1272                }
1273                return video;
1274            } catch (e) {
1275                try { Sentry.captureException(e, { tags: { area: 'video', op: 'generateVideo' } }); } catch (_e) {}
1276                const fallback = document.createElement('video');
1277                try { fallback.muted = true; } catch (_e) {}
1278                return fallback;
1279            }
1280        }
1281
1282        if(ulElement){
1283            document.querySelector('.ribbon-li-desktop-live').addEventListener('mouseenter', function () {
1284                if (!this.getAttribute('data-initialized')) {
1285                    setupLiveDropdown(varValues.genderLong, varValues.liveSexDropdownGenderTier, !!user);
1286                    this.setAttribute('data-initialized', 'true');
1287                    const script = document.createElement('script');
1288                    script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
1289                    document.head.appendChild(script);
1290                }
1291            });
1292        }
1293
1294        async function initializeStripChatBanners(models) {
1295            try {
1296                const aznlive_banner = document.getElementById('aznlive-banner');
1297                const banner_items = document.getElementsByClassName('aznlive-banner__item');
1298
1299                if(aznlive_banner) aznlive_banner.style.display = 'block';
1300
1301                if (varValues.gender === 'fans') {
1302                    const headerTitle = aznlive_banner.querySelector('.aznlive-banner__header h3');
1303                    if (headerTitle) {
1304                        if (varValues.genderLong === 'male') {
1305                            headerTitle.innerHTML = 'Chat with <span>live</span> guys';
1306                        } else if (varValues.genderLong === 'trans') {
1307                            headerTitle.innerHTML = 'Chat with <span>live</span> trans girls';
1308                        } else {
1309                            headerTitle.innerHTML = 'Chat with <span>live</span> girls';
1310                        }
1311                    }
1312
1313                    const footerLink = aznlive_banner.querySelector('.aznlive-banner__footer a');
1314                    const footerButtonRight = aznlive_banner.querySelector('.aznlive-banner__button-right p');
1315                    if (footerLink && footerButtonRight) {
1316                        const genderPath = varValues.genderLong === 'male' ? 'men' : varValues.genderLong === 'trans' ? 'trans' : 'girls';
1317                        const sourceId = 'azfans-video';
1318
1319                        footerLink.href = `https://aznudelive.com/${genderPath}?targetDomain=aznudelive.com&p=verify50af&sourceId=${sourceId}&userId=e5832eb0a24a6150f7d3967ae12ea6e95a0e3ce83557ea66397b6ed5ba72751e`;
1320
1321                        if (varValues.genderLong === 'male') {
1322                            footerButtonRight.textContent = 'Cam Guys';
1323                        } else if (varValues.genderLong === 'trans') {
1324                            footerButtonRight.textContent = 'Cam Trans Girls';
1325                            footerButtonRight.style.fontSize = '14px';
1326                        } else {
1327                            footerButtonRight.textContent = 'Cam Girls';
1328                        }
1329                    }
1330                }
1331
1332                for (let i = 0; i < 6; i++) {
1333                    const { username, snapshotUrl, stream } = models[i];
1334
1335                    if(banner_items.length){
1336                        banner_items[i].classList.add('aznlive-banner__item-is-live');
1337
1338                        const sourceIdBanner = `sourceId=aznude-video`;
1339                        const fallbackUrlBanner = `https://aznudelive.com/${username}?${sourceIdBanner}&userId=e5832eb0a24a6150f7d3967ae12ea6e95a0e3ce83557ea66397b6ed5ba72751e&targetDomain=aznudelive.com/${varValues.aznudeLiveCamOutUrl}&p=verify50af`
1340                        const hrefBanner = user ? `${baseUrl}&${sourceIdBanner}&site=/${username}&fallbackUrl=${encodeURIComponent(fallbackUrlBanner)}` : fallbackUrlBanner;
1341
1342                        banner_items[i].setAttribute('data-video-src', stream.url);
1343                        banner_items[i].setAttribute('href', hrefBanner);
1344                        banner_items[i].style.backgroundImage = `url('${snapshotUrl}')`;
1345                        banner_items[i].innerHTML = `<p>${username}</p>`;
1346                    }
1347                }
1348
1349                document.querySelectorAll('.aznlive-banner__item').forEach(wrapper => {
1350                    wrapper.addEventListener('mouseenter', async function () {
1351                        try {
1352                            await loadHlsScript();
1353                            const vid_src = this.getAttribute('data-video-src');
1354                            if (this.getAttribute('data-video-initialized') !== 'true') {
1355                                if (this.hls) {
1356                                    this.hls.destroy();
1357                                }
1358                                this.setAttribute('data-video-initialized', 'true');
1359                                const vid = generateVideo(vid_src);
1360                                this.appendChild(vid);
1361                                vid.style.transition = 'opacity 0.5s';
1362                                vid.style.opacity = 0;
1363                                setTimeout(() => vid.style.opacity = 1, 0);
1364                            }
1365                        } catch (err) {
1366                            try { Sentry.captureException(err, { tags: { area: 'banners', op: 'rightHoverInit' } }); } catch (_e) {}
1367                            console.error(err);
1368                        }
1369                    });
1370
1371                    wrapper.addEventListener('mouseleave', function () {
1372                        const video = this.querySelector('video');
1373                        if (video) {
1374                            video.style.transition = 'opacity 0.5s';
1375                            video.style.opacity = 0;
1376                            video.addEventListener('transitionend', () => {
1377                                this.setAttribute('data-video-initialized', 'false');
1378                                const hlsInstance = hlsInstances[video.id];
1379                                if (hlsInstance) {
1380                                    hlsInstance.destroy();
1381                                    delete hlsInstances[video.id];
1382                                }
1383                                video.remove();
1384                            });
1385                        }
1386                    });
1387                });
1388
1389                async function loadHlsScript() {
1390                    if (!document.documentElement.getAttribute('hls-initialized')) {
1391                        document.documentElement.setAttribute('hls-initialized', 'true');
1392                        const script = document.createElement('script');
1393                        script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
1394                        return new Promise((resolve, reject) => {
1395                            script.onload = () => resolve();
1396                            script.onerror = () => reject(new Error('Failed to load HLS script'));
1397                            document.head.appendChild(script);
1398                        });
1399                    }
1400                }
1401
1402                if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
1403                    document.querySelectorAll('.aznlive-horizontal__item').forEach(wrapper => {
1404
1405                        wrapper.addEventListener('touchend', function () {
1406                            const video = this.querySelector('video');
1407                            if (video) {
1408                                video.style.transition = 'opacity 0.5s';
1409                                video.style.opacity = 0;
1410                                video.addEventListener('transitionend', () => video.remove());
1411                            }
1412                        });
1413
1414                        wrapper.addEventListener('touchstart', async function () {
1415                            try {
1416                                await loadHlsScript();
1417                                const vid_src = this.getAttribute('data-video-src');
1418                                const vid = generateVideo(vid_src);
1419                                this.appendChild(vid);
1420                                vid.style.transition = 'opacity 0.5s';
1421                                vid.style.opacity = 0;
1422                                setTimeout(() => vid.style.opacity = 1, 0);
1423                            } catch (err) {
1424                                try { Sentry.captureException(err, { tags: { area: 'banners', op: 'mobileTouchInit' } }); } catch (_e) {}
1425                                console.error(err);
1426                            }
1427                        });
1428                    });
1429                } else {
1430                    document.querySelectorAll('.aznlive-horizontal__item').forEach(wrapper => {
1431                        wrapper.addEventListener('mouseenter', async function () {
1432                            try {
1433                                await loadHlsScript();
1434                                const vid_src = this.getAttribute('data-video-src');
1435                                if (this.getAttribute('data-video-initialized') !== 'true') {
1436                                    if (this.hls) {
1437                                        this.hls.destroy();
1438                                    }
1439                                    this.setAttribute('data-video-initialized', 'true');
1440                                    const vid = generateVideo(vid_src);
1441                                    this.appendChild(vid);
1442                                    vid.style.transition = 'opacity 0.5s';
1443                                    vid.style.opacity = 0;
1444                                    setTimeout(() => vid.style.opacity = 1, 0);
1445                                }
1446                            } catch (err) {
1447                                try { Sentry.captureException(err, { tags: { area: 'banners', op: 'desktopHoverInit' } }); } catch (_e) {}
1448                                console.error(err);
1449                            }
1450                        });
1451
1452                        wrapper.addEventListener('mouseleave', function () {
1453                            const video = this.querySelector('video');
1454                            if (video) {
1455                                video.style.transition = 'opacity 0.5s';
1456                                video.style.opacity = 0;
1457                                video.addEventListener('transitionend', () => {
1458                                    this.setAttribute('data-video-initialized', 'false');
1459                                    const hlsInstance = hlsInstances[video.id];
1460                                    if (hlsInstance) {
1461                                        hlsInstance.destroy();
1462                                        delete hlsInstances[video.id];
1463                                    }
1464                                    video.remove();
1465                                });
1466                            }
1467                        });
1468                    });
1469                }
1470            } catch (error) {
1471                try { Sentry.captureException(error, { tags: { area: 'banners', op: 'rightOfPlayer' } }); } catch (_e) {}
1472                if (document.querySelector('.video-right')) document.querySelector('.video-right').style.display = "none";
1473                if (document.querySelector('.video-left')) document.querySelector('.video-left').style.width = "100%";
1474                console.error(error);
1475            }
1476        }
1477
1478        if (varValues.gender === "fans") {
1479            (async () => {
1480                try {
1481                    const stripChatResponse = await fetch(`https://broad-term-5811.aznude.workers.dev/?gender=${resolveGenderForWorker()}&tag=${varValues.liveSexDropdownGenderTags}`);
1482                    const stripChatData = await stripChatResponse.json();
1483                    const { models } = stripChatData;
1484                    await initializeStripChatBanners(models);
1485                } catch (error) {
1486                    try { Sentry.captureException(error, { tags: { area: 'banners', op: 'fansPageLoad' } }); } catch (_e) {}
1487                    console.error(error);
1488                }
1489            })();
1490        }
1491
1492        const AD_CONFIG = {
1493            [varValues.ad_zones.native4x1_grid]: [
1494                { selector: '#resized1' },
1495                { selector: '#singlePageExo .visible-on-desktop' }
1496            ],
1497            [varValues.ad_zones.banner300x100]: [
1498                { selector: '#resize1', width: 300 },
1499                { selector: '#singlePageExo .visible-on-mobile', width: 300 }
1500            ],
1501            [varValues.ad_zones.native2x1_grid]: [
1502                { selector: '#resize1' },
1503                { selector: '#singlePageExo .visible-on-mobile' }
1504            ],
1505            [varValues.ad_zones.native1x1_mobile]: [
1506                { selector: '#resize1' },
1507                { selector: '#singlePageExo .visible-on-mobile' }
1508            ],
1509            [varValues.ad_zones.native1x1_video]: [
1510                { selector: 'div.media-list-item.exo-item', multi: true, builder: (item, layout) => buildNativeVideo(item, layout) }
1511            ],
1512            [varValues.ad_zones.native4x4_footer]: [
1513                { selector: '#aznude_native' }
1514            ],
1515            [varValues.ad_zones.banner300x100_alt]: [
1516                { selector: '.native-footer-mobile', width: 300 }
1517            ],
1518            [varValues.ad_zones.banner300x250_mobile_footer]: [
1519                { selector: '.native-footer-mobile', width: 300 }
1520            ],
1521            [varValues.ad_zones.native1x1_mobile_footer]: [
1522                { selector: '.native-footer-mobile' }
1523            ],
1524            [varValues.ad_zones.native2x1_mobile_footer]: [
1525                { selector: '.native-footer-mobile' }
1526            ],
1527            [varValues.ad_zones.native2x2_mobile_footer]: [
1528                { selector: '.native-footer-mobile' }
1529            ],
1530            [varValues.ad_zones.banner728x90]: [
1531                { selector: '#singlePageExo .visible-on-desktop', width: 728 }
1532            ],
1533            [varValues.ad_zones.native5x1_grid]: [
1534                { selector: '#single-video-bottom-ad .visible-on-desktop' }
1535            ],
1536            [varValues.ad_zones.banner728x90_alt]: [
1537                { selector: '#single-video-bottom-ad .visible-on-desktop', width: 728 }
1538            ],
1539            [varValues.ad_zones.native1x1_grid]: [
1540                { selector: '#single-video-bottom-ad .visible-on-mobile' }
1541            ],
1542            [varValues.ad_zones.banner300x100_alt2]: [
1543                { selector: '#single-video-bottom-ad .visible-on-mobile', width: 300 }
1544            ],
1545            [varValues.ad_zones.native2x1_grid_alt]: [
1546                { selector: '#single-video-bottom-ad .visible-on-mobile' }
1547            ]
1548        };
1549
1550        delete AD_CONFIG['undefined'];
1551
1552        const NB_CLS = (() => {
1553            const r = `x${Math.random().toString(36).slice(2, 8)}`;
1554            return {
1555                'exo-native-widget-item-image-wrapper': `${r}iw`,
1556                'exo-native-widget-item-image-ratio':   `${r}ir`,
1557                'exo-native-widget-item-content-box':   `${r}cb`,
1558                'exo-native-widget-outer-container':    `${r}oc`,
1559                'exo-native-widget-item-container':     `${r}ic`,
1560                'exo-native-widget-item-content':       `${r}ct`,
1561                'exo-native-widget-item-image':         `${r}im`,
1562                'exo-native-widget-item-title':         `${r}ti`,
1563                'exo-native-widget-item-brand':         `${r}br`,
1564                'exo-native-widget-item-text':          `${r}tx`,
1565                'exo-native-widget-header':             `${r}hd`,
1566                'exo-native-widget-item':               `${r}it`,
1567                'exo-native-widget':                    `${r}wg`
1568            };
1569        })();
1570
1571        const NB_CLS_KEYS = Object.keys(NB_CLS).sort((a, b) => b.length - a.length);
1572
1573        let nbMountSeq = 0;
1574
1575        const NB_LAYOUT_OVERRIDES = {
1576            [varValues.ad_zones.native4x4_footer]: { image_height: '150px', mobile_image_width: 243, mobile_image_height: 150 },
1577
1578            [varValues.ad_zones.native1x1_grid]: {
1579                text_width_pct: 'calc(75% - 20px)',
1580                extra_css: '.exo-native-widget-item-content { height: 34px !important; }'
1581            }
1582        };
1583
1584        function nbEsc(v) {
1585            return String(v == null ? '' : v)
1586                .replace(/&/g, '&amp;')
1587                .replace(/</g, '&lt;')
1588                .replace(/>/g, '&gt;')
1589                .replace(/"/g, '&quot;')
1590                .replace(/'/g, '&#39;');
1591        }
1592
1593        function nbUrl(v) {
1594            const s = String(v == null ? '' : v).trim();
1595            return /^https?:\/\//i.test(s) ? s : '';
1596        }
1597
1598        function nbCssUrl(v) {
1599            return nbUrl(v).replace(/["'()\\]/g, encodeURIComponent);
1600        }
1601
1602        function nbNum(v, fallback) {
1603            const n = parseFloat(v);
1604            return Number.isFinite(n) ? n : fallback;
1605        }
1606
1607        function nbFlow(position, boxSize, imgW, pctOverride) {
1608            const p = String(position == null ? 'bottom' : position).toLowerCase();
1609            const row = (p === 'left' || p === 'right');
1610            const boxPx = nbNum(boxSize, 0);
1611            const auto = (boxPx > 0 && imgW > 0) ? (boxPx / (boxPx + imgW)) * 100 : 50;
1612            const override = nbNum(pctOverride, 0);
1613            const w = (typeof pctOverride === 'string' && pctOverride.indexOf('calc') === 0)
1614                ? pctOverride
1615                : `${(override > 0 && override < 100) ? override : auto}%`;
1616
1617            return {
1618                row: row,
1619                dir: p === 'right' ? 'row'
1620                    : p === 'left' ? 'row-reverse'
1621                    : p === 'top' ? 'column-reverse'
1622                    : 'column',
1623                img: row
1624                    ? 'flex: 1 1 auto; min-width: 0; width: auto; height: auto;'
1625                    : 'flex: initial; width: 100%; height: 100%;',
1626                box: row
1627                    ? `flex: 0 0 ${w}; width: ${w}; max-width: ${w}; height: auto; max-height: none;`
1628                    : `flex: initial; width: 100%; max-width: 100%; height: ${boxSize}; max-height: ${boxSize};`
1629            };
1630        }
1631
1632        function nbMapClasses(s) {
1633            let out = String(s == null ? '' : s);
1634            for (let i = 0; i < NB_CLS_KEYS.length; i++) {
1635                out = out.split(NB_CLS_KEYS[i]).join(NB_CLS[NB_CLS_KEYS[i]]);
1636            }
1637            return out;
1638        }
1639
1640        function nbScopeCss(css, scope) {
1641            if (!css) return '';
1642            const src = String(css).replace(/\/\*[\s\S]*?\*\//g, '');
1643            let out = '';
1644            let i = 0;
1645
1646            while (i < src.length) {
1647                const open = src.indexOf('{', i);
1648                if (open === -1) break;
1649
1650                const prelude = src.slice(i, open).trim();
1651
1652                if (prelude.charAt(0) === '@') {
1653                    let depth = 0;
1654                    let j = open;
1655                    for (; j < src.length; j++) {
1656                        if (src[j] === '{') {
1657                            depth++;
1658                        } else if (src[j] === '}') {
1659                            depth--;
1660                            if (depth === 0) break;
1661                        }
1662                    }
1663                    out += `${prelude}{${nbScopeCss(src.slice(open + 1, j), scope)}}`;
1664                    i = j + 1;
1665                } else {
1666                    let close = src.indexOf('}', open);
1667                    if (close === -1) close = src.length;
1668
1669                    const sel = prelude
1670                        .split(',')
1671                        .map(s => s.trim())
1672                        .filter(Boolean)
1673                        .map(s => `${scope} ${s}`)
1674                        .join(',');
1675
1676                    if (sel) out += `${sel}{${src.slice(open + 1, close)}}`;
1677                    i = close + 1;
1678                }
1679            }
1680
1681            return out;
1682        }
1683
1684        function nbWidgetCss(L, scope) {
1685            const C = NB_CLS;
1686
1687            const perRow  = parseInt(L.itemsPerRow, 10) || 1;
1688            const mobCols = parseInt(L.mobile_cols, 10) || perRow;
1689            const mobRows = parseInt(L.mobile_rows, 10) || 1;
1690            const bp      = parseInt(L.mobile_breakpoint, 10) || 450;
1691
1692            const gapH = nbNum(L.spacing_h, 0);
1693            const gapV = nbNum(L.spacing_v, 0);
1694
1695            const imgW  = nbNum(L.image_width, 0) || 1;
1696            const imgH  = nbNum(L.image_height, 0);
1697            const ratio = (imgH / imgW) * 100;
1698
1699            const font      = L.font_family || 'Arial, Helvetica, Verdana, sans-serif';
1700            const baseSize  = L.header_font_size || '12px';
1701            const baseColor = L.header_font_color || '#999999';
1702            const boxSize   = L.text_box_size || '90px';
1703            const padTop    = L.item_padding || '0px';
1704            const border    = nbNum(L.image_border_size, 0);
1705            const cap       = Math.max(1, mobRows * mobCols);
1706
1707            const flow = nbFlow(L.text_position, boxSize, imgW, L.text_width_pct);
1708
1709            const mImgW    = nbNum(L.mobile_image_width, 0) || imgW;
1710            const mImgH    = nbNum(L.mobile_image_height, 0) || imgH;
1711            const mRatio   = (mImgH / mImgW) * 100;
1712            const mBoxSize = `${nbNum(L.mobile_text_box_size, 0) || nbNum(boxSize, 90)}px`;
1713            const mFlow    = nbFlow(L.mobile_text_position == null ? L.text_position : L.mobile_text_position, mBoxSize, mImgW, L.mobile_text_width_pct == null ? L.text_width_pct : L.mobile_text_width_pct);
1714            const mText    = L.mobile_text_enabled == null ? L.text_enabled : L.mobile_text_enabled;
1715
1716            const wg = `.${C['exo-native-widget']}`;
1717            const hd = `.${C['exo-native-widget-header']}`;
1718            const oc = `.${C['exo-native-widget-outer-container']}`;
1719            const ic = `.${C['exo-native-widget-item-container']}`;
1720            const it = `.${C['exo-native-widget-item']}`;
1721            const iw = `.${C['exo-native-widget-item-image-wrapper']}`;
1722            const im = `.${C['exo-native-widget-item-image']}`;
1723            const ir = `.${C['exo-native-widget-item-image-ratio']}`;
1724            const ct = `.${C['exo-native-widget-item-content']}`;
1725            const cb = `.${C['exo-native-widget-item-content-box']}`;
1726            const ti = `.${C['exo-native-widget-item-title']}`;
1727            const tx = `.${C['exo-native-widget-item-text']}`;
1728            const br = `.${C['exo-native-widget-item-brand']}`;
1729
1730            return `
1731            ${scope}, ${scope} * { box-sizing: border-box; }
1732            ${scope} {
1733                background-color: ${L.widget_background_color || 'transparent'};
1734                overflow: hidden;
1735                max-width: 100%;
1736                width: ${L.widget_width || '100%'};
1737                min-width: 103px;
1738                display: inline-flex;
1739                flex-direction: ${parseInt(L.header_is_on_top, 10) === 0 ? 'column-reverse' : 'column'};
1740                text-align: initial;
1741            }
1742            ${scope} ${hd} {
1743                width: initial;
1744                float: initial;
1745                white-space: nowrap;
1746                font-family: ${font};
1747                font-size: ${baseSize};
1748                color: ${baseColor};
1749                display: ${parseInt(L.header_enabled, 10) === 1 ? 'block' : 'none'};
1750                padding-bottom: 15px;
1751                line-height: 15px;
1752            }
1753            ${scope} ${hd} a {
1754                display: flex;
1755                justify-content: ${L.header_text_align === 'left' ? 'flex-start' : (L.header_text_align === 'center' ? 'center' : 'flex-end')};
1756                color: inherit;
1757                text-decoration: none;
1758            }
1759            ${scope} ${oc} {
1760                display: flex;
1761                flex-wrap: wrap;
1762                height: 100%;
1763                align-content: space-between;
1764                margin-bottom: -${gapV}px;
1765                margin-right: -${gapH}px;
1766                width: inherit;
1767                max-width: 100%;
1768            }
1769            ${scope} ${oc} ${ic} {
1770                overflow: hidden;
1771                flex-basis: calc(100% / ${perRow});
1772                position: relative;
1773                padding-right: ${gapH}px;
1774                padding-bottom: ${gapV}px;
1775            }
1776            ${scope} ${oc} ${ic} a${it} {
1777                text-decoration: none;
1778                font-family: ${font};
1779                font-size: ${baseSize};
1780                color: ${baseColor};
1781                overflow: hidden;
1782                display: flex;
1783                position: relative;
1784                flex-direction: ${flow.dir};
1785            }
1786            ${scope} ${oc} ${ic} a${it} ${iw} {
1787                position: relative;
1788                float: left;
1789                ${flow.img}
1790                z-index: 2;
1791                height: 100%;
1792                overflow: hidden;${border > 0 ? `
1793                border: ${border}px solid ${L.image_border_color || '#000000'};` : ''}
1794            }
1795            ${scope} ${oc} ${ic} a${it} ${ir} {
1796                width: 100%;
1797                padding-top: ${ratio}%;
1798                z-index: 1;
1799            }
1800            ${scope} ${oc} ${ic} a${it} ${im} {
1801                background-position: center;
1802                background-repeat: no-repeat;
1803                background-size: cover;
1804                position: absolute;
1805                top: 0;
1806                left: 0;
1807                width: 100%;
1808                height: 100%;
1809                transition: transform .3s, background-size .3s;
1810            }
1811            ${scope} ${oc} ${ic} a${it} ${im}:hover {
1812                transform: scale(1.05);
1813            }
1814            ${scope} ${oc} ${ic} a${it} ${ct} {
1815                ${flow.box}
1816                overflow: hidden;
1817                right: 0;
1818                top: 0;
1819                padding-left: ${flow.dir === 'row' ? padTop : (L.text_margin_left || '0px')};
1820                padding-right: ${flow.dir === 'row-reverse' ? padTop : (L.text_margin_right || '0px')};
1821                padding-top: ${flow.row ? (L.text_margin_top || '0px') : padTop};
1822                padding-bottom: 0;
1823                display: ${parseInt(L.text_enabled, 10) === 0 ? 'none' : 'block'};
1824                text-align: ${L.text_align || 'left'};
1825                position: relative;
1826            }
1827            ${scope} ${oc} ${ic} a${it} ${ct} ${cb} {
1828                position: absolute;
1829                width: 100%;
1830            }
1831            ${scope} ${oc} ${ic} a${it} ${ct} ${ti} {
1832                font-family: ${font};
1833                font-size: ${L.title_font_size || '12px'};
1834                color: ${L.title_font_color || '#ffffff'};
1835                font-weight: ${L.title_font_weight || 'normal'};
1836                text-decoration: ${L.title_decoration || 'none'};
1837                display: ${parseInt(L.title_enabled, 10) === 1 ? 'block' : 'none'};
1838                transition: color 0.16s;
1839            }
1840            ${scope} ${oc} ${ic} a${it} ${ct} ${tx} {
1841                font-family: ${font};
1842                font-size: ${L.description_font_size || '11px'};
1843                color: ${L.description_font_color || '#555555'};
1844                font-weight: ${L.description_font_weight || 'normal'};
1845                text-decoration: ${L.description_decoration || 'none'};
1846                display: ${parseInt(L.description_enabled, 10) === 1 ? 'block' : 'none'};
1847                transition: color 0.16s;
1848            }
1849            ${scope} ${oc} ${ic} a${it} ${ct} ${br} {
1850                font-family: ${font};
1851                font-size: ${L.brand_font_size || '11px'};
1852                color: ${L.brand_font_color || '#555555'};
1853                font-weight: ${L.brand_font_weight || 'normal'};
1854                text-decoration: ${L.brand_decoration || 'none'};
1855                display: ${parseInt(L.brand_enabled, 10) === 1 ? 'block' : 'none'};
1856            }
1857            ${scope} ${oc} ${ic} a${it} ${ct} ${ti}:hover {
1858                color: ${L.title_hover_color || L.title_font_color || '#ffffff'};
1859                font-weight: ${L.title_hover_font_weight || L.title_font_weight || 'normal'};
1860                text-decoration: ${L.title_hover_decoration || 'none'};
1861            }
1862            ${scope} ${oc} ${ic} a${it} ${ct} ${tx}:hover {
1863                color: ${L.description_hover_color || L.description_font_color || '#555555'};
1864                font-weight: ${L.description_hover_font_weight || L.description_font_weight || 'normal'};
1865                text-decoration: ${L.description_hover_decoration || 'none'};
1866            }
1867            ${scope} ${ti}, ${scope} ${tx} {
1868                white-space: nowrap;
1869                overflow: hidden;
1870                text-overflow: ellipsis;
1871            }
1872            @media all and (max-width: ${bp}px) {
1873                ${scope} ${oc} ${ic}:nth-child(n + ${cap + 1}) { display: none; }
1874                ${scope} ${oc} ${ic} a${it} { flex-direction: ${mFlow.dir}; }
1875                ${scope} ${oc} ${ic} a${it} ${iw} { ${mFlow.img} }
1876                ${scope} ${oc} ${ic} a${it} ${ir} { padding-top: ${mRatio}%; }
1877                ${scope} ${oc} ${ic} a${it} ${ct} { ${mFlow.box} padding-left: ${mFlow.dir === 'row' ? padTop : (L.text_margin_left || '0px')}; padding-right: ${mFlow.dir === 'row-reverse' ? padTop : (L.text_margin_right || '0px')}; padding-top: ${mFlow.row ? (L.text_margin_top || '0px') : padTop}; display: ${parseInt(mText, 10) === 0 ? 'none' : 'block'}; }
1878                ${scope} ${oc} ${ic} { flex-basis: calc(100% / ${mobCols}); }
1879                ${scope} { width: 100% !important; height: auto !important; }
1880                ${scope} ${ic} { clear: both; max-width: 100% !important; margin-left: 0 !important; }
1881                ${scope} ${it} ${im} { width: 100% !important; max-width: 100% !important; margin: auto !important; }
1882                ${scope} ${ic} ${it} ${ct} { max-width: 100% !important; }
1883                ${scope} ${ic} ${it} { margin: 0 auto !important; }
1884            }`;
1885        }
1886
1887        function buildNbNativeWidget(zoneData, idzone) {
1888            const C = NB_CLS;
1889            const base = (zoneData && zoneData.data && zoneData.data.layout) || {};
1890            const L = Object.assign({}, base, NB_LAYOUT_OVERRIDES[idzone] || {});
1891            const items = (zoneData && zoneData.data && zoneData.data.data) || [];
1892
1893            if (!items.length) return '';
1894
1895            const cap = (parseInt(L.itemsPerRow, 10) || 1) * (parseInt(L.itemsPerCol, 10) || 1);
1896            const shown = items.slice(0, cap || items.length);
1897
1898            const mountId = `az-cw-${++nbMountSeq}`;
1899            const scope = `#${mountId}`;
1900
1901            const newTab = parseInt(L.open_in_new_window, 10) === 1;
1902            const target = newTab ? ' target="_blank"' : '';
1903
1904            const itemsHTML = shown.map(item => {
1905                const href = nbUrl(item.url);
1906                if (!href) return '';
1907                const img = nbCssUrl(item.optimum_image || item.image);
1908                const pos = nbEsc(item.image_position || '50% 50%');
1909
1910                return `<div class="${C['exo-native-widget-item-container']}">
1911                    <a class="${C['exo-native-widget-item']}" href="${nbEsc(href)}" rel="nofollow noopener"${target}>
1912                        <div class="${C['exo-native-widget-item-image-wrapper']}">
1913                            <div class="${C['exo-native-widget-item-image']}" style="background-image: url(&quot;${img}&quot;); background-position: ${pos};"></div>
1914                            <div class="${C['exo-native-widget-item-image-ratio']}"></div>
1915                        </div>
1916                        <div class="${C['exo-native-widget-item-content']}">
1917                            <div class="${C['exo-native-widget-item-title']}">${nbEsc(item.title)}</div>
1918                            <div class="${C['exo-native-widget-item-text']}">${nbEsc(item.description)}</div>
1919                            <div class="${C['exo-native-widget-item-bran
1919d']}">${nbEsc(item.brand)}</div>
1920                        </div>
1921                    </a>
1922                </div>`;
1923            }).join('');
1924
1925            if (!itemsHTML) return '';
1926
1927            let css = nbWidgetCss(L, scope);
1928            if (parseInt(L.customcss_enabled, 10) === 1 && L.customcss) {
1929                css += `\n${nbScopeCss(nbMapClasses(L.customcss), scope)}`;
1930            }
1931            if (L.extra_css) {
1932                css += `\n${nbScopeCss(nbMapClasses(L.extra_css), scope)}`;
1933            }
1934
1935            return `<div style="text-align:center;max-width:100%"><style>${css}</style><div id="${mountId}" class="${C['exo-native-widget']}">
1936                <div class="${C['exo-native-widget-header']}"></div>
1937                <div class="${C['exo-native-widget-outer-container']}">${itemsHTML}</div>
1938            </div></div>`;
1939        }
1940
1941        function buildSimpleBanner(data, width, layout) {
1942            const href = nbUrl(data.url);
1943            const img = nbUrl(data.image);
1944            if (!href || !img) return '';
1945            return `<a href="${nbEsc(href)}" rel="nofollow noopener" target="_blank"><img src="${nbEsc(img)}" alt="" style="width: 100%; max-width: ${parseInt(width, 10) || 300}px; height: auto" /></a>`;
1946        }
1947
1948        function buildNativeVideo(item, layout) {
1949            const href = nbUrl(item.url);
1950            if (!href) return '';
1951            const img = nbCssUrl(item.optimum_image || item.image);
1952            return `
1953                <div class="videos-list__line-ad">
1954                    <span class="video-ad-label">AD</span>
1955                    <a href="${nbEsc(href)}" rel="nofollow noopener" target="_blank" class="exo-native-az-box-wrapper">
1956                        <div class="exo-native-az-box-img-wrapper">
1957                            <div class="exo-native-az-box-img" style="background-position: ${nbEsc(item.image_position || '50% 50%')}; background-image: url(&quot;${img}&quot;);"></div>
1958                        </div>
1959                        <div class="exo-native-widget-item-content">
1960                            <div class="exo-native-widget-item-title">${nbEsc(item.title)}</div>
1961                            <div class="exo-native-widget-item-text">${nbEsc(item.description)}</div>
1962                        </div>
1963                    </a>
1964                </div>
1965            `;
1966        }
1967
1968        function cleanupFailedAds() {
1969            $(document).ready(function() {
1970                $(
1971                '#resize1, #resized1, .az-footer, #aznude_native,' +
1972                '.clearfix.visible-lg, #exo-native-ad-box-clearfix1, #exo-native-az-box-clearfix1,' +
1973                '#exo-native-az-container, .native-footer-mobile,' +
1974                '#singlePageExo .visible-on-desktop, #singlePageExo .visible-on-mobile,' +
1975                '#single-video-bottom-ad,' +
1976                '.single-video-sidebar-container'
1977                ).each(function () {
1978                    this.style.setProperty('display', 'none', 'important');
1979                });
1980
1981                $('.single-video-content-wrapper').css('max-width', '100%')
1982
1983                $('div.media-list-item.exo-item').remove();
1984                
1985                $('ins[data-zoneid], ins.eas6a97888e, ins.eas6a97888e2, ins.adsbyexoclick').each(function() {
1986                    $(this).hide();
1987                });
1988                
1989            });
1990        }
1991
1992        async function fetchNeverblockWithTimeout(zoneId, timeout = 2000) {
1993            const controller = new AbortController();
1994            const timeoutId = setTimeout(() => controller.abort(), timeout);
1995            
1996            if (typeof nbDomain !== 'undefined' && nbDomain) {
1997                try {
1998                    const response = await fetch(
1999                        `${nbDomain}/?zones=[${zoneId}]`,
2000                        { signal: controller.signal }
2001                    );
2002                    clearTimeout(timeoutId);
2003                    
2004                    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
2005
2006                    const data = await response.json();
2007                    if (!data || !data.zones || data.zones.length === 0) throw new Error('Empty or invalid ad response from rotated domain');
2008
2009                    const zone = data.zones[0];
2010                    if (!zone || !zone.idzone || !zone.data) throw new Error('Invalid zone data structure from rotated domain');
2011
2012                    return data;
2013                } catch (error) {
2014                    clearTimeout(timeoutId);
2015                    try { 
2016                        Sentry.captureException(new Error(`Rotated domain failed (${nbDomain}): ${error.message}`), { 
2017                            tags: { area: 'ads', op: 'rotatedDomainFallback' } 
2018                        }); 
2019                    } catch (_e) {}
2020                }
2021            }
2022            
2023            const fallbackController = new AbortController();
2024            const fallbackTimeoutId = setTimeout(() => fallbackController.abort(), timeout);
2025            
2026            try {
2027                const response = await fetch(
2028                    `https://az-neverblock.aznude.workers.dev/?zones=[${zoneId}]`,
2029                    { signal: fallbackController.signal }
2030                );
2031                clearTimeout(fallbackTimeoutId);
2032                
2033                if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
2034
2035                const data = await response.json();
2036                
2037                if (!data || !data.zones || data.zones.length === 0) throw new Error('Empty or invalid ad response');
2038
2039                const zone = data.zones[0];
2040                if (!zone || !zone.idzone || !zone.data) throw new Error('Invalid zone data structure');
2041
2042                return data;
2043            } catch (error) {
2044                clearTimeout(fallbackTimeoutId);
2045                if (error.name === 'AbortError') {
2046                    throw new Error('Neverblock request timed out');
2047                }
2048                throw error;
2049            }
2050        }
2051
2052        function injectAd(zoneId, zoneData) {
2053            const configs = AD_CONFIG[zoneId];
2054            if (!configs || !zoneData) return;
2055
2056            const isNative = zoneData.type === 'native_ad';
2057            const layout = isNative ? zoneData.data.layout : {};
2058            const data = isNative ? zoneData.data.data : zoneData.data;
2059
2060            configs.forEach(config => {
2061                const elements = config.multi
2062                    ? Array.from(document.querySelectorAll(config.selector))
2063                    : [document.querySelector(config.selector)].filter(Boolean);
2064
2065                if (!elements.length) return;
2066
2067                elements.forEach((el, idx) => {
2068                    let html;
2069
2070                    if (config.builder) {
2071                        // Rotate through the returned items so repeated slots don't
2072                        // all render the identical creative.
2073                        html = config.multi && isNative
2074                            ? config.builder(data[idx % data.length], layout)
2075                            : config.builder(data, layout);
2076                    } else if (isNative) {
2077                        // Fresh mount id per element so the scoped CSS never collides.
2078                        html = buildNbNativeWidget(zoneData, zoneId);
2079                    } else {
2080                        html = buildSimpleBanner(data, config.width || 300, layout);
2081                    }
2082
2083                    if (!html) return;
2084
2085                    if (config.multi && zoneData.type === 'banner') {
2086                        el.parentNode.innerHTML = html;
2087                    } else {
2088                        el.innerHTML = html;
2089                    }
2090                });
2091            });
2092        }
2093
2094        (async () => {
2095            try {
2096                if(!(await userHasAdBlockerOn())) return;
2097            } catch(e) {
2098                console.error('Ad blocker check failed:', e);
2099                return;
2100            }
2101            
2102            const countryCode = await getCountryCode();
2103            if(countryCode === "GB" || countryCode === "AU") {
2104                let verified = isAgeVerified();
2105                
2106                if (!verified && auth.currentUser) verified = await isAgeVerifiedInDB(auth.currentUser.uid);
2107                if (!verified) return cleanupFailedAds();
2108            }
2109    
2110            let adsFailed = false;
2111            let stripChatData = null;
2112            
2113            try {
2114                if (varValues.gender !== "fans") {
2115                    stripChatData = await fetch(
2116                        `https://broad-term-5811.aznude.workers.dev/?gender=${resolveGenderForWorker()}&tag=${varValues.liveSexDropdownGenderTags}`
2117                    ).then(r => r.json());
2118                }
2119            } catch (e) {
2120                try { Sentry.captureException(e, { tags: { area: 'ads', op: 'stripChat' } }); } catch (_e) {}
2121            }
2122
2123            if($('#age-modal-ad')){
2124                $('#age-modal-ad').hide();
2125            }
2126
2127            const useStripChatFooter = stripChatData && Math.random() >= 0.5;
2128
2129            if (document.querySelector('#resized1') || document.querySelector('#singlePageExo .visible-on-desktop')) {
2130                try {
2131                    const isSinglePage = !!document.querySelector('#singlePageExo');
2132                    let desktopZoneId;
2133                    
2134                    if (isSinglePage) {
2135                        const desktopRandom = Math.random();
2136                        desktopZoneId = desktopRandom < 0.5 
2137                            ? varValues.ad_zones.banner728x90 
2138                            : varValues.ad_zones.native4x1_grid;
2139                    } else {
2140                        desktopZoneId = varValues.ad_zones.native4x1_grid;
2141                    }
2142                    
2143                    const data = await fetchNeverblockWithTimeout(desktopZoneId);
2144                    data.zones.forEach(zone => {
2145                        if (zone && zone.idzone) {
2146                            injectAd(zone.idzone, zone);
2147                        }
2148                    });
2149                } catch (error) {
2150                    adsFailed = true;
2151                }
2152            }
2153
2154            if (document.querySelector('#resize1') || document.querySelector('#singlePageExo .visible-on-mobile')) {
2155                try {
2156                    const mobileHeaderRandom = Math.random();
2157                    let mobileHeaderZoneId;
2158                    if (mobileHeaderRandom < 0.3333) {
2159                        mobileHeaderZoneId = varValues.ad_zones.banner300x100;
2160                    } else if (mobileHeaderRandom < 0.6666) {
2161                        mobileHeaderZoneId = varValues.ad_zones.native2x1_grid;
2162                    } else {
2163                        mobileHeaderZoneId = varValues.ad_zones.native1x1_mobile;
2164                    }
2165                    
2166                    const data = await fetchNeverblockWithTimeout(mobileHeaderZoneId);
2167                    data.zones.forEach(zone => {
2168                        if (zone && zone.idzone) {
2169                            injectAd(zone.idzone, zone);
2170                        }
2171                    });
2172                } catch (error) {
2173                    adsFailed = true;
2174                }
2175            }
2176
2177            if (document.querySelectorAll('div.media-list-item.exo-item').length > 0) {
2178                try {
2179                    const data = await fetchNeverblockWithTimeout(varValues.ad_zones.native1x1_video);
2180                    data.zones.forEach(zone => {
2181                        if (zone && zone.idzone) {
2182                            injectAd(zone.idzone, zone);
2183                        }
2184                    });
2185                } catch (error) {
2186                    adsFailed = true;
2187                }
2188            }
2189
2190            if (document.querySelector('#aznude_native') && !useStripChatFooter) {
2191                try {
2192                    const data = await fetchNeverblockWithTimeout(varValues.ad_zones.native4x4_footer);
2193                    data.zones.forEach(zone => {
2194                        if (zone && zone.idzone) {
2195                            injectAd(zone.idzone, zone);
2196                        }
2197                    });
2198                } catch (error) {
2199                    adsFailed = true;
2200                }
2201            }
2202
2203            if (document.querySelector('.native-footer-mobile')) {
2204                try {
2205                    const mobileFooterRandom = Math.random();
2206                    let mobileFooterZoneId;
2207                    if (mobileFooterRandom < 0.2) {
2208                        mobileFooterZoneId = varValues.ad_zones.banner300x100_alt;
2209                    } else if (mobileFooterRandom < 0.4) {
2210                        mobileFooterZoneId = varValues.ad_zones.banner300x250_mobile_footer;
2211                    }
2211 else if (mobileFooterRandom < 0.6) {
2212                        mobileFooterZoneId = varValues.ad_zones.native2x2_mobile_footer;
2213                    } else if (mobileFooterRandom < 0.8) {
2214                        mobileFooterZoneId = varValues.ad_zones.native2x1_mobile_footer;
2215                    } else {
2216                        mobileFooterZoneId = varValues.ad_zones.native1x1_mobile_footer;
2217                    }
2218                    
2219                    const data = await fetchNeverblockWithTimeout(mobileFooterZoneId);
2220                    data.zones.forEach(zone => {
2221                        if (zone && zone.idzone) {
2222                            injectAd(zone.idzone, zone);
2223                        }
2224                    });
2225                } catch (error) {
2226                    adsFailed = true;
2227                }
2228            }
2229
2230            if (document.querySelector('#single-video-bottom-ad .visible-on-desktop')) {
2231                try {
2232                    const videoBottomDesktopRandom = Math.random();
2233                    const videoBottomDesktopZoneId = videoBottomDesktopRandom < 0.5 
2234                        ? varValues.ad_zones.native5x1_grid 
2235                        : varValues.ad_zones.banner728x90_alt;
2236                    
2237                    const data = await fetchNeverblockWithTimeout(videoBottomDesktopZoneId);
2238                    data.zones.forEach(zone => {
2239                        if (zone && zone.idzone) {
2240                            injectAd(zone.idzone, zone);
2241                        }
2242                    });
2243                } catch (error) {
2244                    adsFailed = true;
2245                }
2246            }
2247
2248            if (document.querySelector('#single-video-bottom-ad .visible-on-mobile')) {
2249                try {
2250                    const videoBottomMobileRandom = Math.random();
2251                    let videoBottomMobileZoneId;
2252                    if (videoBottomMobileRandom < 0.3333) {
2253                        videoBottomMobileZoneId = varValues.ad_zones.native1x1_grid;
2254                    } else if (videoBottomMobileRandom < 0.6666) {
2255                        videoBottomMobileZoneId = varValues.ad_zones.banner300x100_alt2;
2256                    } else {
2257                        videoBottomMobileZoneId = varValues.ad_zones.native2x1_grid_alt;
2258                    }
2259                    
2260                    const data = await fetchNeverblockWithTimeout(videoBottomMobileZoneId);
2261                    data.zones.forEach(zone => {
2262                        if (zone && zone.idzone) {
2263                            injectAd(zone.idzone, zone);
2264                        }
2265                    });
2266                } catch (error) {
2267                    adsFailed = true;
2268                }
2269            }
2270            
2271            if (stripChatData) {
2272                if (useStripChatFooter) {
2273                    try {
2274                        $('.az-footer').hide();
2275                        $('.single-video-4x4-ad-container').hide();
2276                        const aznlive_mobile_banner = document.getElementById('aznlive-horizontal__banner');
2277                        const banner_mobile_items = document.getElementsByClassName('aznlive-horizontal__item');
2278
2279                        if(aznlive_mobile_banner) aznlive_mobile_banner.style.display = 'flex';
2280
2281                        for (let i = 0; i < 6; i++) {
2282                            const { username, snapshotUrl, stream } = stripChatData.models[i];
2283
2284                            if(banner_mobile_items.length){
2285                                const sourceIdMobile = `sourceId=aznude-footer`;
2286                                const fallbackUrlMobile = `https://aznudelive.com/${username}?${sourceIdMobile}&userId=e5832eb0a24a6150f7d3967ae12ea6e95a0e3ce83557ea66397b6ed5ba72751e&targetDomain=aznudelive.com/${varValues.aznudeLiveCamOutUrl}&p=verify50af`
2287                                const hrefMobile = user ? `${baseUrl}&${sourceIdMobile}&site=/${username}&fallbackUrl=${encodeURIComponent(fallbackUrlMobile)}` : fallbackUrlMobile;
2288
2289                                banner_mobile_items[i].classList.add('aznlive-horizontal__item-is-live');
2290                                banner_mobile_items[i].setAttribute('data-video-src', stream.url);
2291                                banner_mobile_items[i].setAttribute('href', hrefMobile);
2292                                banner_mobile_items[i].style.backgroundImage = `url('${snapshotUrl}')`;
2293                                banner_mobile_items[i].innerHTML = '';
2294                            }
2295                        }
2296                    } catch (error) {
2297                        try { Sentry.captureException(error, { tags: { area: 'banners', op: 'mobileFooterBanner' } }); } catch (_e) {}
2298                        document.getElementById('aznlive-horizontal__banner').style.display = "none";
2299                        if(document.querySelector('.single-video-4x4-ad-container')) document.querySelector('.single-video-4x4-ad-container').style.display = "none";
2300                    }
2301                }
2302
2303                try {
2304                    await initializeStripChatBanners(stripChatData.models);
2305                } catch (error) {
2306                    try { Sentry.captureException(error, { tags: { area: 'ads', op: 'stripChatBanners' } }); } catch (_e) {}
2307                }
2308            }
2309            
2310            if (adsFailed) {
2311                cleanupFailedAds();
2312            }
2313        })();
2314    });
2315});

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.