PageSourceSearch

https://cdn.azfans.com/js/new-main/v75/new-main.js

js azfans.com collected 2026-10-02 12:42:13 UTC 266,839 bytes, 5,317 lines download raw bytes

1//rythm is a dancer
2const urls = {
3    signupOnAZNudeLive: 'https://users.aznude.com/app/signupOnAZNudeLive',
4    signinToAZNudeLive: 'https://users.aznude.com/app/signinToAZNudeLive',
5    approveUpload: 'https://users.aznude.com/app/approveUpload',
6    declineUpload: 'https://users.aznude.com/app/declineUpload',
7    deleteUser: 'https://users.aznude.com/app/deleteUser',
8    disableUser: 'https://users.aznude.com/app/disableUser',
9    reenableUser: 'https://users.aznude.com/app/reenableUser',
10    onLogin: 'https://users.aznude.com/app/onLogin',
11    signupUser: 'https://users.aznude.com/app/signupUser',
12    verifyRecaptcha: 'https://users.aznude.com/app/verifyRecaptcha',
13    camOut: 'https://users.aznude.com/app/camOut',
14    declineUploadAndBanUser: 'https://users.aznude.com/app/declineUploadAndBanUser',
15    checkUsername: 'https://users.aznude.com/app/checkUsername',
16    signupWithSocial: 'https://users.aznude.com/app/signupWithSocial',
17    signinWithSocial: 'https://users.aznude.com/app/signinWithSocial',
18
19    // signupOnAZNudeLive: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/signupOnAZNudeLive',
20    // signinToAZNudeLive: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/signinToAZNudeLive',
21    // approveUpload: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/approveUpload',
22    // declineUpload: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/declineUpload',
23    // deleteUser: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/deleteUser',
24    // disableUser: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/disableUser',
25    // reenableUser: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/reenableUser',
26    // onLogin: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/onLogin',
27    // signupUser: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/signupUser',
28    // verifyRecaptcha: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/verifyRecaptcha',
29    // camOut: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/camOut',
30    // declineUploadAndBanUser: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/declineUploadAndBanUser',
31    // checkUsername: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/checkUsername',
32    // signupWithSocial: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/signupWithSocial',
33    // signinWithSocial: 'http://localhost:1597/aznude-fea6a/us-central1/main/app/signinWithSocial',
34};
35
36const firebaseErrorMessages = {
37    "auth/wrong-password": "The password is invalid or the user does not have a password.",
38    "auth/claims-too-large": "The claims payload provided to setCustomUserClaims() exceeds the maximum allowed size of 1000 bytes.",
39    "auth/email-already-exists": "The provided email is already in use by an existing user. Each user must have a unique email.",
40    "auth/id-token-expired": "The provided Firebase ID token is expired.",
41    "auth/id-token-revoked": "The Firebase ID token has been revoked.",
42    "auth/insufficient-permission": "The credential used to initialize the Admin SDK has insufficient permission to access the requested Authentication resource. Refer to Set up a Firebase project for documentation on how to generate a credential with appropriate permissions and use it to authenticate the Admin SDKs.",
43    "auth/invalid-argument": "An invalid argument was provided to an Authentication method. The error message should contain additional information.",
44    "auth/invalid-claims": "The custom claim attributes provided to setCustomUserClaims() are invalid.",
45    "auth/invalid-creation-time": "The creation time must be a valid UTC date string.",
46    "auth/invalid-disabled-field": "The provided value for the disabled user property is invalid. It must be a boolean.",
47    "auth/invalid-display-name": "The provided value for the displayName user property is invalid. It must be a non-empty string.",
48    "auth/invalid-email-verified": "The provided value for the emailVerified user property is invalid. It must be a boolean.",
49    "auth/invalid-hash-algorithm": "The hash algorithm must match one of the strings in the list of supported algorithms.",
50    "auth/invalid-hash-block-size": "The hash block size must be a valid number.",
51    "auth/invalid-hash-derived-key-length": "The hash derived key length must be a valid number.",
52    "auth/invalid-hash-key": "The hash key must a valid byte buffer.",
53    "auth/invalid-hash-memory-cost": "The hash memory cost must be a valid number.",
54    "auth/invalid-hash-parallelization": "The hash parallelization must be a valid number.",
55    "auth/invalid-hash-rounds": "The hash rounds must be a valid number.",
56    "auth/invalid-hash-salt-separator": "The hashing algorithm salt separator field must be a valid byte buffer.",
57    "auth/invalid-id-token": "The provided ID token is not a valid Firebase ID token.",
58    "auth/invalid-last-sign-in-time": "The last sign-in time must be a valid UTC date string.",
59    "auth/invalid-page-token": "The provided next page token in listUsers() is invali
59d. It must be a valid non-empty string.",
60    "auth/invalid-password": "The provided value for the password user property is invalid. It must be a string with at least six characters.",
61    "auth/invalid-password-hash": "The password hash must be a valid byte buffer.",
62    "auth/invalid-password-salt": "The password salt must be a valid byte buffer",
63    "auth/invalid-photo-url": "The provided value for the photoURL user property is invalid. It must be a string URL.",
64    "auth/invalid-provider-data": "The providerData must be a valid array of UserInfo objects.",
65    "auth/invalid-oauth-responsetype": "Only exactly one OAuth responseType should be set to true.",
66    "auth/invalid-session-cookie-duration": "The session cookie duration must be a valid number in milliseconds between 5 minutes and 2 weeks.",
67    "auth/invalid-uid": "The provided uid must be a non-empty string with at most 128 characters.",
68    "auth/invalid-user-import": "The user record to import is invalid.",
69    "auth/maximum-user-count-exceeded": "The maximum allowed number of users to import has been exceeded.",
70    "auth/missing-hash-algorithm": "Importing users with password hashes requires that the hashing algorithm and its parameters be provided.",
71    "auth/missing-uid": "A uid identifier is required for the current operation.",
72    "auth/missing-oauth-client-secret": "The OAuth configuration client secret is required to enable OIDC code flow.",
73    "auth/phone-number-already-exists": "The provided phoneNumber is already in use by an existing user. Each user must have a unique phoneNumber.",
74    "auth/project-not-found": "No Firebase project was found for the credential used to initialize the Admin SDKs. Refer to Set up a Firebase project for documentation on how to generate a credential for your project and use it to authenticate the Admin SDKs.",
75    "auth/reserved-claims": "One or more custom user claims provided to setCustomUserClaims() are reserved. For example, OIDC specific claims such as (sub, iat, iss, exp, aud, auth_time, etc) should not be used as keys for custom claims.",
76    "auth/session-cookie-expired": "The provided Firebase session cookie is expired.",
77    "auth/session-cookie-revoked": "The Firebase session cookie has been revoked.",
78    "auth/uid-already-exists": "The provided uid is already in use by an existing user. Each user must have a unique uid.",
79    "auth/admin-restricted-operation": "This operation is restricted to administrators only.",
80    "auth/app-not-authorized": "This app, identified by the domain where it's hosted, is not authorized to use Firebase Authentication with the provided API key. Review your key configuration in the Google API console.",
81    "auth/app-not-installed": "The requested mobile application corresponding to the identifier (Android package name or iOS bundle ID) provided is not installed on this device.",
82    "auth/captcha-check-failed": "The reCAPTCHA response token provided is either invalid, expired, already used or the domain associated with it does not match the list of whitelisted domains.",
83    "auth/code-expired": "The SMS code has expired. Please re-send the verification code to try again.",
84    "auth/cordova-not-ready": "Cordova framework is not ready.",
85    "auth/cors-unsupported": "This browser is not supported.",
86    "auth/credential-already-in-use": "This email is already associated with a different user account.",
87    "auth/custom-token-mismatch": "The custom token corresponds to a different audience.",
88    "auth/requires-recent-login": "This operation is sensitive and requires recent authentication. Log in again before retrying this request.",
89    "auth/dependent-sdk-initialized-before-auth": "Another Firebase SDK was initialized and is trying to use Auth before Auth is initialized. Please be sure to call `initializeAuth` or `getAuth` before starting any other Firebase SDK.",
90    "auth/dynamic-link-not-activated": "Please activate Dynamic Links in the Firebase Console and agree to the terms and conditions.",
91    "auth/email-change-needs-verification": "Multi-factor users must al
91ways have a verified email.",
92    "auth/email-already-in-use": "The email address is already in use by another account.",
93    "auth/emulator-config-failed": "Auth instance has already been used to make a network call. Auth can no longer be configured to use the emulator. Try calling 'connectAuthEmulator()' sooner.",
94    "auth/expired-action-code": "The action code has expired.",
95    "auth/cancelled-popup-request": "This operation has been cancelled due to another conflicting popup being opened.",
96    "auth/internal-error": "An internal AuthError has occurred.",
97    "auth/invalid-app-credential": "The phone verification request contains an invalid application verifier. The reCAPTCHA token response is either invalid or expired.",
98    "auth/invalid-app-id": "The mobile app identifier is not registed for the current project.",
99    "auth/invalid-user-token": "This user's credential isn't valid for this project. This can happen if the user's token has been tampered with, or if the user isn't for the project associated with this API key.",
100    "auth/invalid-auth-event": "An internal AuthError has occurred.",
101    "auth/invalid-verification-code": "The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure to use the verification code provided by the user.",
102    "auth/invalid-continue-uri": "The continue URL provided in the request is invalid.",
103    "auth/invalid-cordova-configuration": "The following Cordova plugins must be installed to enable OAuth sign-in: cordova-plugin-buildinfo, cordova-universal-links-plugin, cordova-plugin-browsertab, cordova-plugin-inappbrowser and cordova-plugin-customurlscheme.",
104    "auth/invalid-custom-token": "The custom token format is incorrect. Please check the documentation.",
105    "auth/invalid-dynamic-link-domain": "The provided dynamic link domain is not configured or authorized for the current project.",
106    "auth/invalid-email": "The email address is badly formatted.",
107    "auth/invalid-emulator-scheme": "Emulator URL must start with a valid scheme (http:// or https://).",
108    "auth/invalid-api-key": "Your API key is invalid, please check you have copied it correctly.",
109    "auth/invalid-cert-hash": "The SHA-1 certificate hash provided is invalid.",
110    "auth/invalid-credential": "The supplied auth credential is malformed or has expired.",
111    "auth/invalid-message-payload": "The email template corresponding to this action contains invalid characters in its message. Please fix by going to the Auth email templates section in the Firebase Console.",
112    "auth/invalid-multi-factor-session": "The request does not contain a valid proof of first factor successful sign-in.",
113    "auth/invalid-oauth-provider": "EmailAuthProvider is not supported for this operation. This operation only supports OAuth providers.",
114    "auth/invalid-oauth-client-id": "The OAuth client ID provided is either invalid or does not match the specified API key.",
115    "auth/unauthorized-domain": "This domain is not authorized for OAuth operations for your Firebase project. Edit the list of authorized domains from the Firebase console.",
116    "auth/invalid-action-code": "The action code is invalid. This can happen if the code is malformed, expired, or has already been used.",
117    "auth/invalid-persistence-type": "The specified persistence type is invalid. It can only be local, session or none.",
118    "auth/invalid-phone-number": "The format of the phone number provided is incorrect. Please enter the phone number in a format that can be parsed into E.164 format. E.164 phone numbers are written in the format [+][country code][subscriber number including area code].",
119    "auth/invalid-provider-id": "The specified provider ID is invali
119d.",
120    "auth/invalid-recipient-email": "The email corresponding to this action failed to send as the provided recipient email address is invalid.",
121    "auth/invalid-sender": "The email template corresponding to this action contains an invalid sender email or name. Please fix by going to the Auth email templates section in the Firebase Console.",
122    "auth/invalid-verification-id": "The verification ID used to create the phone auth credential is invalid.",
123    "auth/invalid-tenant-id": "The Auth instance's tenant ID is invalid.",
124    "auth/missing-android-pkg-name": "An Android Package Name must be provided if the Android App is required to be installed.",
125    "auth/auth-domain-config-required": "Be sure to include authDomain when calling firebase.initializeApp(), by following the instructions in the Firebase console.",
126    "auth/missing-app-credential": "The phone verification request is missing an application verifier assertion. A reCAPTCHA response token needs to be provided.",
127    "auth/missing-verification-code": "The phone auth credential was created with an empty SMS verification code.",
128    "auth/missing-continue-uri": "A continue URL must be provided in the request.",
129    "auth/missing-iframe-start": "An internal AuthError has occurred.",
130    "auth/missing-ios-bundle-id": "An iOS Bundle ID must be provided if an App Store ID is provided.",
131    "auth/missing-or-invalid-nonce": "The request does not contain a valid nonce. This can occur if the SHA-256 hash of the provided raw nonce does not match the hashed nonce in the ID token payload.",
132    "auth/missing-multi-factor-info": "No second factor identifier is provided.",
133    "auth/missing-multi-factor-session": "The request is missing proof of first factor successful sign-in.",
134    "auth/missing-phone-number": "To send verification codes, provide a phone number for the recipient.",
135    "auth/missing-verification-id": "The phone auth credential was created with an empty verification ID.",
136    "auth/app-deleted": "This instance of FirebaseApp has been deleted.",
137    "auth/multi-factor-info-not-found": "The user does not have a second factor matching the identifier provided.",
138    "auth/multi-factor-auth-required": "Proof of ownership of a second factor is required to complete sign-in.",
139    "auth/account-exists-with-different-credential": "An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address.",
140    "auth/network-request-failed": "A network AuthError (such as timeout, interrupted connection or unreachable host) has occurred.",
141    "auth/no-auth-event": "An internal AuthError has occurred.",
142    "auth/no-such-provider": "User was not linked to an account with the given provider.",
143    "auth/null-user": "A null user object was provided as the argument for an operation which requires a non-null user object.",
144    "auth/operation-not-allowed": "The given sign-in provider is disabled for this Firebase project. Enable it in the Firebase console, under the sign-in method tab of the Auth section.",
145    "auth/operation-not-supported-in-this-environment": "This operation is not supported in the environment this application is running on. 'location.protocol' must be http, https or chrome-extension and web storage must be enabled.",
146    "auth/popup-blocked": "Unable to establish a connection with the popup. It may have been blocked by the browser.",
147    "auth/popup-closed-by-user": "The popup has been closed by the user before finalizing the operation.",
148    "auth/provider-already-linked": "User can only be linked to one identity for the given provider.",
149    "auth/quota-exceeded": "The project's quota for this operation has been exceeded.",
150    "auth/redirect-cancelled-by-user": "The redirect operation has been cancelled by the user before finalizing.",
151    "auth/redirect-operation-pending": "A redirect sign-in operation is already pending.",
152    "auth/rejected-credential": "The request contains malformed or mismatching credentials.",
153    "auth/second-factor-already-in-use": "The second factor is already enrolled on this account.",
154    "auth/maximum-second-factor-count-exceeded": "The maximum allowed number of second factors on a user has been exceeded.",
155    "auth/tenant-id-mismatch": "The provided tenant ID does not match the Auth instance's tenant ID",
156    "auth/timeout": "The operation has timed out.",
157    "auth/user-token-expired": "The user's credential is no longer valid. The user must sign in again.",
158    "auth/too-many-requests": "We have blocked all requests from this device due to unusual activity. Try again later.",
159    "auth/unauthorized-continue-uri": "The domain of the continue URL is not whitelisted.  Please whitelist the domain in the Firebase console.",
160    "auth/unsupported-first-factor": "Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.",
161    "auth/unsupported-persistence-type": "The current environment does not support the specified persistence type.",
162    "auth/unsupported-tenant-operation": "This operation is not supported in a multi-tenant context.",
163    "auth/unverified-email": "The operation requires a verified email.",
164    "auth/user-cancelled": "The user did not grant your application the permissions it requested.",
165    "auth/user-not-found": "There is no user record corresponding to this identifier. The user may have been deleted.",
166    "auth/user-disabled": "The user account has been disabled by an administrator.",
167    "auth/user-mismatch": "The supplied credentials do not correspond to the previously signed in user.",
168    "auth/weak-password": "The password must be 6 characters long or more.",
169    "auth/web-storage-unsupported": "This browser is not supported or 3rd party cookies and data may be disabled.",
170    "auth/already-initialized": "initializeAuth() has already been called with different options. To avoid this error, call initializeAuth() with the same options as when it was originally called, or call getAuth() to return the already initialized instance."
171}
172
173async function loadScript(url) {
174    return new Promise((resolve, reject) => {
175        const script = document.createElement('script');
176        script.src = url;
177        script.onload = resolve;
178        script.onerror = reject;
179        document.head.appendChild(script);
180    });
181}
182
183async function loadStylesheet(url) {
184    return new Promise((resolve, reject) => {
185        const link = document.createElement('link');
186        link.rel = 'stylesheet';
187        link.href = url;
188        link.type = 'text/css';
189        link.onload = resolve;
190        link.onerror = reject;
191        document.head.appendChild(link);
192    });
193}
194
195function showMessageByID(message, selector) {
196    $(selector).hide();
197    $(selector).html(message);
198    $(selector).fadeIn();
199}
200
201function showMessage(message) {
202    $('#alert-message').hide();
203    $('#alert-message').html(message);
204    $('#alert-message').fadeIn();
205    $('html, body').animate(
206        {
207            scrollTop: $('#alert-message').offset().top - 100,
208        },
209        0,
210    );
211}
212
213function hideMessage() {
214    $('#alert-message').html('');
215    $('#alert-message').hide();
216}
217
218function convertDate(timestamp) {
219    const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
220    const d = new Date(timestamp);
221    return `${('00' + d.getDate()).slice(-2)} ${monthNames[d.getMonth()]} ${d.getFullYear()} ${('00' + d.getHours()).slice(-2)}:${(
222        '00' + d.getMinutes()
223    ).slice(-2)}`;
224}
225
226function convertDateDDMMYYYY(timestamp) {
227    const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
228    const d = new Date(timestamp);
229    return `${('00' + d.getDate()).slice(-2)} ${monthNames[d.getMonth()]} ${d.getFullYear()}`;
230}
231
232function getQueryStringValue(key) {
233    return decodeURIComponent(
234        window.location.search.replace(
235            new RegExp('^(?:.*[&\\?]' + encodeURIComponent(key).replace(/[\.\+\*]/g, '\\$&') + '(?:\\=([^&]*))?)?.*$', 'i'),
236            '$1',
237        ),
238    );
239}
240
241function formatBytes(bytes, decimals) {
242    if (bytes == 0) return '0 Byte';
243    var k = 1000;
244    var dm = decimals + 1 || 3;
245    var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
246    var i = Math.floor(Math.log(bytes) / Math.log(k));
247    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
248}
249
250function isValidDate(dateString) {
251    if (dateString === '01/01/0001') return true;
252
253    if (!/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(dateString)) return false;
254
255    var parts = dateString.split('/');
256    var day = parseInt(parts[1], 10);
257    var month = parseInt(parts[0], 10);
258    var year = parseInt(parts[2], 10);
259
260    if (year < 1000 || year > 3000 || month == 0 || month > 12) return false;
261
262    var monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
263
264    if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) monthLength[1] = 29;
265
266    return day > 0 && day <= monthLength[month - 1];
267}
268
269function isImageFile(file) {
270    const extension = file.substr(file.lastIndexOf('.') + 1);
271
272    switch (extension.toLowerCase()) {
273        case 'jpg':
274        case 'png':
275        case 'jpeg':
276        case 'gif':
277            return true;
278            break;
279        default:
280            return false;
281    }
282}
283
284function sanitize_xss(string) {
285    const map = {
286        '&': '',
287        '<': '',
288        '>': '',
289        '"': '',
290        "'": '',
291        '/': '',
292    };
293    const reg = /[&<>"'/]/gi;
294    return string.replace(reg, (match) => map[match]);
295}
296
297function getRandomString(length) {
298    var randomChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
299    var result = '';
300    for (var i = 0; i < length; i++) {
301        result += randomChars.charAt(Math.floor(Math.random() * randomChars.length));
302    }
303    return result;
304}
305
306function constructAZNLiveNavbarLink(url) {
307    const site = new URL(url).pathname;
308    const queryStrings = url.substring(url.indexOf('?') + 1);
309
310    return `?site=${site}&${queryStrings}`;
311}
312
313function debounce(func, wait) {
314    let timeout;
315    return function(...args) {
316        const later = () => {
317            clearTimeout(timeout);
318            func.apply(this, args);
319        };
320        clearTimeout(timeout);
321        timeout = setTimeout(later, wait);
322    };
323}
324
325async function loadScript(url) {
326    return new Promise((resolve, reject) => {
327        const script = document.createElement('script');
328        script.src = url;
329        script.onload = resolve;
330        script.onerror = reject;
331        document.head.appendChild(script);
332    });
333}
334
335async function loadStylesheet(url) {
336    return new Promise((resolve, reject) => {
337        const link = document.createElement('link');
338        link.rel = 'stylesheet';
339        link.href = url;
340        link.type = 'text/css';
341        link.onload = resolve;
342        link.onerror = reject;
343        document.head.appendChild(link);
344    });
345}
346
347const $navbar = $(".account-dropdown");
348const $navbaroverlay = $(".account-dropdown-overlay");
349const $navbarToggler = $(".account-navbar__toggler");
350
351$(function() {
352    const next = encodeURIComponent(window.location.href);
353
354    $('a[href="/signin.html"]').attr('href', '/signin.html?next=' + next);
355
356    function closeNavbar() {
357        $navbarToggler.attr("data-nav-status", "closed");
358        $navbar.removeClass("account-dropdown--show");
359        $navbaroverlay.removeClass("account-dropdown-overlay--show");
360        setTimeout(() => {
361            $navbar.hide();
362            $navbaroverlay.hide();
363            $(document.body).css("overflow", "auto");
364        }, window.screen.width < 1024 ? 100 : 0);
365    }
366
367    function openNavbar() {
368        $navbarToggler.attr("data-nav-status", "open");
369        $navbar.show();
370        $navbaroverlay.show();
371        setTimeout(() => {
372            $navbar.addClass("account-dropdown--show");
373            $navbaroverlay.addClass("account-dropdown-overlay--show");
374            if (window.innerWidth < 1024) {
375                $(document.body).css("overflow", "hidden");
376            }
377        }, 50);
378    }
379
380    $(".account-dropdown__close").on('click', closeNavbar)
381    $navbarToggler.on("click", function () {
382        const $this = $(this);
383        if ($this.attr("data-nav-status") === "closed") {
384            openNavbar();
385        } else {
386            closeNavbar();
387        }
388    });
389
390    function fixDropdownPosition() {
391        const $navbarTogglerDesktop = $(".account-navbar__toggler--desktop");
392        const navbarTogglerOffset = $navbarTogglerDesktop.offset();
393        $navbar.css({
394            top: navbarTogglerOffset.top + $navbarTogglerDesktop.outerHeight() + 10,
395            left: navbarTogglerOffset.left - $navbar.outerWidth() + $navbarTogglerDesktop.outerWidth() - 10,
396        });
397        if(window.innerWidth < 1024) {
398            $(document.body).css("overflow", "hidden")
399        } else {
400            $(document.body).css("overflow", "auto")
401        }
402    }
403
404    $(window).on("resize", debounce(function () {
405        if ($navbar.is(":visible")) {
406            fixDropdownPosition();
407        }
408    }, 1));
409
410    $(document).on("click", function(event) {
411        if (!$(event.target).closest(".account-navbar__toggler").length && !$(event.target).closest(".modal").length && !$(event.target).closest(".account-dropdown").length) {
412            if ($navbar.is(":visible")) {
413                closeNavbar()
414            }
415        }
416    });
417
418    function toggleHistoryModal() {
419        const modalId = String(Date.now());
420        // Signed in, the switch is the account's (AZ-157): off here is off on every
421        // device signed in to it. new-firebase-init.js provides window.azHistorySwitch.
422        const historySwitch = window.azHistorySwitch;
423        const accountWide = !!(historySwitch && historySwitch.accountWide());
424        const offCopy = accountWide
425            ? 'This stops saving what you view on every device signed in to your account.'
426            : 'You can disable history to stop saving what you view on this device.';
427
428        const dialog = document.createElement('dialog');
429        dialog.className = 'report-modal';
430        dialog.id = 'history-modal-' + modalId;
431        dialog.setAttribute('aria-labelledby', 'history-modal-title-' + modalId);
432        dialog.style.display = 'none';
433
434        dialog.innerHTML = `
435            <style>
436                .btn-cancel, .btn-cancel:hover {
437                    background-color: #2e2e2e6b
438                }
439
440                .modal-copy {
441                    font-size: 13px;
442                }
443
444                .btn-cancel, .btn-confirm {
445                    padding: 7px 10px;
446                }
447            </style>
448            <div class="report-modal-viewport">
449                <div class="report-modal-content" role="document" aria-modal="true">
450                    <header>
451                        <h2 id="history-modal-title-${modalId}" style="display: none;">Disable Viewing History</h2>
452                        <button type="button" class="report-close-modal" aria-label="Close modal">×</button>
453                    </header>
454                    <form class="report-form history-form" novalidate>
455                        <h3 class="section-title" style="margin-top: 0;">Turn off viewing history?</h3>
456                        <p class="modal-copy">${offCopy}</p>
457                        <section class="checkbox-group" style="margin-top: 12px;">
458                            <input type="checkbox" id="modal-clear-history-${modalId}">
459                            <label for="modal-clear-history-${modalId}" style="margin-bottom: 0">Also clear existing history</label>
460                        </section>
461                        <div class="history-actions" style="margin-bottom: 0; margin-top: 30px">
462                            <button type="button" class="report-btn-submit btn-cancel">Cancel</button>
463                            <button type="submit" class="report-btn-submit btn-confirm">
464                                <span>Continue</span>
465                                <svg class="loading-spinner" width="16" height="16" viewBox="0 0 50 50" style="display:none; margin-left:8px; vertical-align:middle;">
466                                    <circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="5" stroke-dasharray="31.4 31.4"></circle>
467                                </svg>
468                            </button>
469                        </div>
470                        <div class="history-message" id="history-error-${modalId}" role="alert" style="display:none; margin-top:10px; font-size:13px; color:red;"></div>
471                    </form>
472                </div>
473            </div>`;
474
475        document.body.appendChild(dialog);
476
477        function openDialog() {
478            document.body.style.overflow = 'hidden';
479            dialog.style.display = 'flex';
480            dialog.showModal();
481        }
482
483        function closeDialog() {
484            try { dialog.close(); } catch(e) {}
485            dialog.style.display = 'none';
486            document.body.style.overflow = '';
487            cleanup();
488        }
489
490        function cleanup() {
491            if (dialog && dialog.parentNode) {
492                dialog.parentNode.removeChild(dialog);
493            }
494        }
495
496        function showError(msg) {
497            const el = document.getElementById('history-error-' + modalId);
498            if (!el) return;
499            el.textContent = msg;
500            el.style.display = 'block';
501        }
502
503        function hideError() {
504            const el = document.getElementById('history-error-' + modalId);
505            if (!el) return;
506            el.textContent = '';
507            el.style.display = 'none';
508        }
509
510        const content = dialog.querySelector('.report-modal-content');
511        const form = dialog.querySelector('.history-form');
512        const cancelBtn = dialog.querySelector('.btn-cancel');
513        const closeBtn = dialog.querySelector('.report-close-modal');
514        const submitBtn = dialog.querySelector('.btn-confirm');
515        const submitText = submitBtn.querySelector('span');
516        const spinner = submitBtn.querySelector('.loading-spinner');
517        const clearCheckbox = dialog.querySelector('#modal-clear-history-' + modalId);
518
519        dialog.addEventListener('click', function (e) {
520            if (!e.target.closest('.report-modal-content')) {
521                closeDialog();
522            }
523        });
524
525        cancelBtn.addEventListener('click', closeDialog);
526        closeBtn.addEventListener('click', closeDialog);
527
528        form.addEventListener('submit', async function (e) {
529            e.preventDefault();
530            hideError();
531
532            submitBtn.disabled = true;
533            submitText.textContent = 'Working...';
534            spinner.style.display = 'inline';
535
536            let turnedOff = false;
537            try {
538                // The switch before any clear. Signed in, off takes effect here
539                // even when the account cannot be reached (new-firebase-init.js
540                // keeps it pending and retries).
541                if (historySwitch) {
542                    await historySwitch.set(false);
543                } else {
544                    try {
545                        localStorage.setItem('history-enabled', 'false');
546                    } catch (storageErr) {}
547                }
548                turnedOff = true;
549
550                if (clearCheckbox && clearCheckbox.checked) {
551                    // The account first (window.azHistory): if it fails the
552                    // local list stays, or it would come back from the account.
553                    if (window.azHistory) {
554                        await window.azHistory.clear(['videos', 'celebs', 'movies', 'stories']);
555                    }
556                    try {
557                        localStorage.removeItem('history-videos');
558                        localStorage.removeItem('history-celebs');
559                        localStorage.removeItem('history-movies');
560                        localStorage.removeItem('history-stories');
561                        // the story pairs new-firebase-init.js keeps beside that list
562                        localStorage.removeItem('history-stories-sid');
563                    } catch (innerErr) {}
564                }
565
566                try {
567                    window.historyEnabled = 'false';
568                } catch (memErr) {}
569
570                closeDialog();
571            } catch (error) {
572                showError('Something went wrong. Please try again.');
573            } finally {
574                submitBtn.disabled = false;
575                submitText.textContent = 'Continue';
576                spinner.style.display = 'none';
577
578                try {
579                    const toggler = document.querySelector('.account-dropdown__history-toggler__input');
580                    if (toggler) toggler.checked = !turnedOff;
581                } catch (toggleErr) {}
582            }
583        });
584
585        dialog.addEventListener('close', cleanup);
586
587        openDialog();
588    }
589
590    // Set while an account write from the toggler is in flight, so a second click
591    // cannot race it.
592    let historySwitchBusy = false;
593
594    $(".account-dropdown__history-toggler").on("click", function() {
595        const input = $(".account-dropdown__history-toggler__input");
596        if (historySwitchBusy) {
597            input.prop("checked", window.azHistorySwitch ? window.azHistorySwitch.isOn() : input.prop("checked"));
598            return;
599        }
600        const checked = input.prop("checked");
601        if(checked) {
602            toggleHistoryModal()
603        } else {
604            input.prop("checked", true);
605            historyEnabled = "true"
606            if (window.azHistorySwitch) {
607                // Signed in, an account write: if it fails, show the switch as it is.
608                historySwitchBusy = true;
609                window.azHistorySwitch.set(true).catch(function () {
610                    input.prop("checked", window.azHistorySwitch.isOn());
611                }).finally(function () {
612                    historySwitchBusy = false;
613                });
614            } else {
615                localStorage.setItem("history-enabled", 'true');
616            }
617        }
618    });
619});
620
621function getCookie(name) {
622    const nameEQ = `${name}=`;
623    return document.cookie.split(';').reduce((acc, c) => {
624        c = c.trim();
625        return c.startsWith(nameEQ) ? c.substring(nameEQ.length) : acc;
626    }, null);
627}
628
629function setCookie(name, value, days) {
630    const date = new Date();
631    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
632    document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`;
633}
634
635
636/*! Magnific Popup - v1.1.0 - 2016-02-20
637* http://dimsemenov.com/plugins/magnific-popup/
638* Copyright (c) 2016 Dmitry Semenov; */
639!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):window.jQuery||window.Zepto)}(function(a){var b,c,d,e,f,g,h="Close",i="BeforeClose",j="AfterClose",k="BeforeAppend",l="MarkupParse",m="Open",n="Change",o="mfp",p="."+o,q="mfp-ready",r="mfp-removing",s="mfp-prevent-close",t=function(){},u=!!window.jQuery,v=a(window),w=function(a,c){b.ev.on(o+a+p,c)},x=function(b,c,d,e){var f=document.createElement("div");return f.className="mfp-"+b,d&&(f.innerHTML=d),e?c&&c.appendChild(f):(f=a(f),c&&f.appendTo(c)),f},y=function(c,d){b.ev.triggerHandler(o+c,d),b.st.callbacks&&(c=c.charAt(0).toLowerCase()+c.slice(1),b.st.callbacks[c]&&b.st.callbacks[c].apply(b,a.isArray(d)?d:[d]))},z=function(c){return c===g&&b.currTemplate.closeBtn||(b.currTemplate.closeBtn=a(b.st.closeMarkup.replace("%title%",b.st.tClose)),g=c),b.currTemplate.closeBtn},A=function(){a.magnificPopup.instance||(b=new t,b.init(),a.magnificPopup.instance=b)},B=function(){var a=document.createElement("p").style,b=["ms","O","Moz","Webkit"];if(void 0!==a.transition)return!0;for(;b.length;)if(b.pop()+"Transition"in a)return!0;return!1};t.prototype={constructor:t,init:function(){var c=navigator.appVersion;b.isLowIE=b.isIE8=document.all&&!document.addEventListener,b.isAndroid=/android/gi.test(c),b.isIOS=/iphone|ipad|ipod/gi.test(c),b.supportsTransition=B(),b.probablyMobile=b.isAndroid||b.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),d=a(document),b.popupsCache={}},open:function(c){var e;if(c.isObj===!1){b.items=c.items.toArray(),b.index=0;var g,h=c.items;for(e=0;e<h.length;e++)if(g=h[e],g.parsed&&(g=g.el[0]),g===c.el[0]){b.index=e;break}}else b.items=a.isArray(c.items)?c.items:[c.items],b.index=c.index||0;if(b.isOpen)return void b.updateItemHTML();b.types=[],f="",c.mainEl&&c.mainEl.length?b.ev=c.mainEl.eq(0):b.ev=d,c.key?(b.popupsCache[c.key]||(b.popupsCache[c.key]={}),b.currTemplate=b.popupsCache[c.key]):b.currTemplate={},b.st=a.extend(!0,{},a.magnificPopup.defaults,c),b.fixedContentPos="auto"===b.st.fixedContentPos?!b.probablyMobile:b.st.fixedContentPos,b.st.modal&&(b.st.closeOnContentClick=!1,b.st.closeOnBgClick=!1,b.st.showCloseBtn=!1,b.st.enableEscapeKey=!1),b.bgOverlay||(b.bgOverlay=x("bg").on("click"+p,function(){b.close()}),b.wrap=x("wrap").attr("tabindex",-1).on("click"+p,function(a){b._checkIfClose(a.target)&&b.close()}),b.container=x("container",b.wrap)),b.contentContainer=x("content"),b.st.preloader&&(b.preloader=x("preloader",b.container,b.st.tLoading));var i=a.magnificPopup.modules;for(e=0;e<i.length;e++){var j=i[e];j=j.charAt(0).toUpperCase()+j.slice(1),b["init"+j].call(b)}y("BeforeOpen"),b.st.showCloseBtn&&(b.st.closeBtnInside?(w(l,function(a,b,c,d){c.close_replaceWith=z(d.type)}),f+=" mfp-close-btn-in"):b.wrap.append(z())),b.st.alignTop&&(f+=" mfp-align-top"),b.fixedContentPos?b.wrap.css({overflow:b.st.overflowY,overflowX:"hidden",overflowY:b.
639st.overflowY}):b.wrap.css({top:v.scrollTop(),position:"absolute"}),(b.st.fixedBgPos===!1||"auto"===b.st.fixedBgPos&&!b.fixedContentPos)&&b.bgOverlay.css({height:d.height(),position:"absolute"}),b.st.enableEscapeKey&&d.on("keyup"+p,function(a){27===a.keyCode&&b.close()}),v.on("resize"+p,function(){b.updateSize()}),b.st.closeOnContentClick||(f+=" mfp-auto-cursor"),f&&b.wrap.addClass(f);var k=b.wH=v.height(),n={};if(b.fixedContentPos&&b._hasScrollBar(k)){var o=b._getScrollbarSize();o&&(n.marginRight=o)}b.fixedContentPos&&(b.isIE7?a("body, html").css("overflow","hidden"):n.overflow="hidden");var r=b.st.mainClass;return b.isIE7&&(r+=" mfp-ie7"),r&&b._addClassToMFP(r),b.updateItemHTML(),y("BuildControls"),a("html").css(n),b.bgOverlay.add(b.wrap).prependTo(b.st.prependTo||a(document.body)),b._lastFocusedEl=document.activeElement,setTimeout(function(){b.content?(b._addClassToMFP(q),b._setFocus()):b.bgOverlay.addClass(q),d.on("focusin"+p,b._onFocusIn)},16),b.isOpen=!0,b.updateSize(k),y(m),c},close:function(){b.isOpen&&(y(i),b.isOpen=!1,b.st.removalDelay&&!b.isLowIE&&b.supportsTransition?(b._addClassToMFP(r),setTimeout(function(){b._close()},b.st.removalDelay)):b._close())},_close:function(){y(h);var c=r+" "+q+" ";if(b.bgOverlay.detach(),b.wrap.detach(),b.container.empty(),b.st.mainClass&&(c+=b.st.mainClass+" "),b._removeClassFromMFP(c),b.fixedContentPos){var e={marginRight:""};b.isIE7?a("body, html").css("overflow",""):e.overflow="",a("html").css(e)}d.off("keyup"+p+" focusin"+p),b.ev.off(p),b.wrap.attr("class","mfp-wrap").removeAttr("style"),b.bgOverlay.attr("class","mfp-bg"),b.container.attr("class","mfp-container"),!b.st.showCloseBtn||b.st.closeBtnInside&&b.currTemplate[b.currItem.type]!==!0||b.currTemplate.closeBtn&&b.currTemplate.closeBtn.detach(),b.st.autoFocusLast&&b._lastFocusedEl&&a(b._lastFocusedEl).focus(),b.currItem=null,b.content=null,b.currTemplate=null,b.prevHeight=0,y(j)},updateSize:function(a){if(b.isIOS){var c=document.documentElement.clientWidth/window.innerWidth,d=window.innerHeight*c;b.wrap.css("height",d),b.wH=d}else b.wH=a||v.height();b.fixedContentPos||b.wrap.css("height",b.wH),y("Resize")},updateItemHTML:function(){var c=b.items[b.index];b.contentContainer.detach(),b.content&&b.content.detach(),c.parsed||(c=b.parseEl(b.index));var d=c.type;if(y("BeforeChange",[b.currItem?b.currItem.type:"",d]),b.currItem=c,!b.currTemplate[d]){var f=b.st[d]?b.st[d].markup:!1;y("FirstMarkupParse",f),f?b.currTemplate[d]=a(f):b.currTemplate[d]=!0}e&&e!==c.type&&b.container.removeClass("mfp-"+e+"-holder");var g=b["get"+d.charAt(0).toUpperCase()+d.slice(1)](c,b.currTemplate[d]);b.appendContent(g,d),c.preloaded=!0,y(n,c),e=c.type,b.container.prepend(b.contentContainer),y("AfterChange")},appendContent:function(a,c){b.content=a,a?b.st.showCloseBtn&&b.st.closeBtnInside&&b.currTemplate[c]===!0?b.content.find(".mfp-close").length||b.content.append(z()):b.content=a:b.content="",y(k),b.container.addClass("mfp-"+c+"-holder"),b.contentContainer.append(b.content)},parseEl:function(c){var d,e=b.items[c];if(e.tagName?e={el:a(e)}:(d=e.type,e={data:e,src:e.src}),e.el){for(var f=b.types,g=0;g<f.length;g++)if(e.el.hasClass("mfp-"+f[g])){d=f[g];break}e.src=e.el.attr("data-mfp-src"),e.src||(e.src=e.el.attr("href"))}return e.type=d||b.st.type||"inline",e.index=c,e.parsed=!0,b.items[c]=e,y("ElementParse",e),b.items[c]},addGroup:function(a,c){var d=function(d){d.mfpEl=this,b._openClick(d,a,c)};c||(c={});var e="click.magnificPopup";c.mainEl=a,c.items?(c.isObj=!0,a.off(e).on(e,d)):(c.isObj=!1,c.delegate?a.off(e).on(e,c.delegate,d):(c.items=a,a.off(e).on(e,d)))},_openClick:function(c,d,e){var f=void 0!==e.midClick?e.midClick:a.magnificPopup.defaults.midClick;if(f||!(2===c.which||c.ctrlKey||c.metaKey||c.altKey||c.shiftKey)){var g=void 0!==e.disableOn?e.disableOn:a.magnificPopup.defaults.disableOn;if(g)if(a.isFunction(g)){if(!g.call(b))return!0}else if(v.width()<g)return!0;c.type&&(c.preventDefault(),b.isOpen&&c.stopPropagation()),e.el=a(c.mfpEl),e.delegate&&(e.items=d.find(e.delegate)),b.open(e)}},updateStatus:function(a,d){if(b.preloader){c!==a&&b.container.removeClass("mfp-s-"+c),d||"loading"!==a||(d=b.st.tLoading);var e={status:a,text:d};y("UpdateStatus",e),a=e.status,d=e.text,b.preloader.html(d),b.preloader.find("a").on("click",function(a){a.stopImmediatePropagation()}),b.container.addClass("mfp-s-"+a),c=a}},_checkIfClose:function(c){if(!a(c).hasClass(s)){var d=b.st.closeOnContentClick,e=b.st.closeOnBgClick;if(d&&e)return!0;
639if(!b.content||a(c).hasClass("mfp-close")||b.preloader&&c===b.preloader[0])return!0;if(c===b.content[0]||a.contains(b.content[0],c)){if(d)return!0}else if(e&&a.contains(document,c))return!0;return!1}},_addClassToMFP:function(a){b.bgOverlay.addClass(a),b.wrap.addClass(a)},_removeClassFromMFP:function(a){this.bgOverlay.removeClass(a),b.wrap.removeClass(a)},_hasScrollBar:function(a){return(b.isIE7?d.height():document.body.scrollHeight)>(a||v.height())},_setFocus:function(){(b.st.focus?b.content.find(b.st.focus).eq(0):b.wrap).focus()},_onFocusIn:function(c){return c.target===b.wrap[0]||a.contains(b.wrap[0],c.target)?void 0:(b._setFocus(),!1)},_parseMarkup:function(b,c,d){var e;d.data&&(c=a.extend(d.data,c)),y(l,[b,c,d]),a.each(c,function(c,d){if(void 0===d||d===!1)return!0;if(e=c.split("_"),e.length>1){var f=b.find(p+"-"+e[0]);if(f.length>0){var g=e[1];"replaceWith"===g?f[0]!==d[0]&&f.replaceWith(d):"img"===g?f.is("img")?f.attr("src",d):f.replaceWith(a("<img>").attr("src",d).attr("class",f.attr("class"))):f.attr(e[1],d)}}else b.find(p+"-"+c).html(d)})},_getScrollbarSize:function(){if(void 0===b.scrollbarSize){var a=document.createElement("div");a.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(a),b.scrollbarSize=a.offsetWidth-a.clientWidth,document.body.removeChild(a)}return b.scrollbarSize}},a.magnificPopup={instance:null,proto:t.prototype,modules:[],open:function(b,c){return A(),b=b?a.extend(!0,{},b):{},b.isObj=!0,b.index=c||0,this.instance.open(b)},close:function(){return a.magnificPopup.instance&&a.magnificPopup.instance.close()},registerModule:function(b,c){c.options&&(a.magnificPopup.defaults[b]=c.options),a.extend(this.proto,c.proto),this.modules.push(b)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'<button title="%title%" type="button" class="mfp-close">&#215;</button>',tClose:"Close (Esc)",tLoading:"Loading...",autoFocusLast:!0}},a.fn.magnificPopup=function(c){A();var d=a(this);if("string"==typeof c)if("open"===c){var e,f=u?d.data("magnificPopup"):d[0].magnificPopup,g=parseInt(arguments[1],10)||0;f.items?e=f.items[g]:(e=d,f.delegate&&(e=e.find(f.delegate)),e=e.eq(g)),b._openClick({mfpEl:e},d,f)}else b.isOpen&&b[c].apply(b,Array.prototype.slice.call(arguments,1));else c=a.extend(!0,{},c),u?d.data("magnificPopup",c):d[0].magnificPopup=c,b.addGroup(d,c);return d};var C,D,E,F="inline",G=function(){E&&(D.after(E.addClass(C)).detach(),E=null)};a.magnificPopup.registerModule(F,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){b.types.push(F),w(h+"."+F,function(){G()})},getInline:function(c,d){if(G(),c.src){var e=b.st.inline,f=a(c.src);if(f.length){var g=f[0].parentNode;g&&g.tagName&&(D||(C=e.hiddenClass,D=x(C),C="mfp-"+C),E=f.after(D).detach().removeClass(C)),b.updateStatus("ready")}else b.updateStatus("error",e.tNotFound),f=a("<div>");return c.inlineElement=f,f}return b.updateStatus("ready"),b._parseMarkup(d,{},c),d}}});var H,I="ajax",J=function(){H&&a(document.body).removeClass(H)},K=function(){J(),b.req&&b.req.abort()};a.magnificPopup.registerModule(I,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'<a href="%url%">The content</a> could not be loaded.'},proto:{initAjax:function(){b.types.push(I),H=b.st.ajax.cursor,w(h+"."+I,K),w("BeforeChange."+I,K)},getAjax:function(c){H&&a(document.body).addClass(H),b.updateStatus("loading");var d=a.extend({url:c.src,success:function(d,e,f){var g={data:d,xhr:f};y("ParseAjax",g),b.appendContent(a(g.data),I),c.finished=!0,J(),b._setFocus(),setTimeout(function(){b.wrap.addClass(q)},16),b.updateStatus("ready"),y("AjaxContentAdded")},error:function(){J(),c.finished=c.loadError=!0,b.updateStatus("error",b.st.ajax.tError.replace("%url%",c.src))}},b.st.ajax.settings);return b.req=a.ajax(d),""}}});var L,M=function(c){if(c.data&&void 0!==c.data.title)return c.data.title;
639var d=b.st.image.titleSrc;if(d){if(a.isFunction(d))return d.call(b,c);if(c.el)return c.el.attr(d)||""}return""};a.magnificPopup.registerModule("image",{options:{markup:'<div class="mfp-figure"><div class="mfp-close"></div><figure><div class="mfp-img"></div><figcaption><div class="mfp-bottom-bar"><div class="mfp-title"></div><div class="mfp-counter"></div></div></figcaption></figure></div>',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'<a href="%url%">The image</a> could not be loaded.'},proto:{initImage:function(){var c=b.st.image,d=".image";b.types.push("image"),w(m+d,function(){"image"===b.currItem.type&&c.cursor&&a(document.body).addClass(c.cursor)}),w(h+d,function(){c.cursor&&a(document.body).removeClass(c.cursor),v.off("resize"+p)}),w("Resize"+d,b.resizeImage),b.isLowIE&&w("AfterChange",b.resizeImage)},resizeImage:function(){var a=b.currItem;if(a&&a.img&&b.st.image.verticalFit){var c=0;b.isLowIE&&(c=parseInt(a.img.css("padding-top"),10)+parseInt(a.img.css("padding-bottom"),10)),a.img.css("max-height",b.wH-c)}},_onImageHasSize:function(a){a.img&&(a.hasSize=!0,L&&clearInterval(L),a.isCheckingImgSize=!1,y("ImageHasSize",a),a.imgHidden&&(b.content&&b.content.removeClass("mfp-loading"),a.imgHidden=!1))},findImageSize:function(a){var c=0,d=a.img[0],e=function(f){L&&clearInterval(L),L=setInterval(function(){return d.naturalWidth>0?void b._onImageHasSize(a):(c>200&&clearInterval(L),c++,void(3===c?e(10):40===c?e(50):100===c&&e(500)))},f)};e(1)},getImage:function(c,d){var e=0,f=function(){c&&(c.img[0].complete?(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("ready")),c.hasSize=!0,c.loaded=!0,y("ImageLoadComplete")):(e++,200>e?setTimeout(f,100):g()))},g=function(){c&&(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("error",h.tError.replace("%url%",c.src))),c.hasSize=!0,c.loaded=!0,c.loadError=!0)},h=b.st.image,i=d.find(".mfp-img");if(i.length){var j=document.createElement("img");j.className="mfp-img",c.el&&c.el.find("img").length&&(j.alt=c.el.find("img").attr("alt")),c.img=a(j).on("load.mfploader",f).on("error.mfploader",g),j.src=c.src,i.is("img")&&(c.img=c.img.clone()),j=c.img[0],j.naturalWidth>0?c.hasSize=!0:j.width||(c.hasSize=!1)}return b._parseMarkup(d,{title:M(c),img_replaceWith:c.img},c),b.resizeImage(),c.hasSize?(L&&clearInterval(L),c.loadError?(d.addClass("mfp-loading"),b.updateStatus("error",h.tError.replace("%url%",c.src))):(d.removeClass("mfp-loading"),b.updateStatus("ready")),d):(b.updateStatus("loading"),c.loading=!0,c.hasSize||(c.imgHidden=!0,d.addClass("mfp-loading"),b.findImageSize(c)),d)}}});var N,O=function(){return void 0===N&&(N=void 0!==document.createElement("p").style.MozTransform),N};a.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(a){return a.is("img")?a:a.find("img")}},proto:{initZoom:function(){var a,c=b.st.zoom,d=".zoom";if(c.enabled&&b.supportsTransition){var e,f,g=c.duration,j=function(a){var b=a.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),d="all "+c.duration/1e3+"s "+c.easing,e={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},f="transition";return e["-webkit-"+f]=e["-moz-"+f]=e["-o-"+f]=e[f]=d,b.css(e),b},k=function(){b.content.css("visibility","visible")};w("BuildControls"+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.content.css("visibility","hidden"),a=b._getItemToZoom(),!a)return void k();f=j(a),f.css(b._getOffset()),b.wrap.append(f),e=setTimeout(function(){f.css(b._getOffset(!0)),e=setTimeout(function(){k(),setTimeout(function(){f.remove(),a=f=null,y("ZoomAnimationEnded")},16)},g)},16)}}),w(i+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.st.removalDelay=g,!a){if(a=b._getItemToZoom(),!a)return;f=j(a)}f.css(b._getOffset(!0)),b.wrap.append(f),b.content.css("visibility","hidden"),setTimeout(function(){f.css(b._getOffset())},16)}}),w(h+d,function(){b._allowZoom()&&(k(),f&&f.remove(),a=null)})}},_allowZoom:function(){return"image"===b.currItem.type},_getItemToZoom:function(){return b.currItem.hasSize?b.currItem.img:!1},_getOffset:function(c){var d;d=c?b.currItem.img:b.st.zoom.opener(b.currItem.el||b.currItem);var e=d.offset(),f=parseInt(d.css("padding-top"),10),g=parseInt(d.css("padding-bottom"),10);e.top-=a(window).scrollTop()-f;var h={width:d.width(),height:(u?d.innerHeight():d[0].offsetHeight)-g-f};return O()?h["-moz-transform"]=h.transform="translate("+e.left+"px,"+e.top+"px)":(h.left=e.left,h.top=e.top),h}}});
639var P="iframe",Q="//about:blank",R=function(a){if(b.currTemplate[P]){var c=b.currTemplate[P].find("iframe");c.length&&(a||(c[0].src=Q),b.isIE8&&c.css("display",a?"block":"none"))}};a.magnificPopup.registerModule(P,{options:{markup:'<div class="mfp-iframe-scaler"><div class="mfp-close"></div><iframe class="mfp-iframe" src="//about:blank" frameborder="0" allowfullscreen></iframe></div>',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){b.types.push(P),w("BeforeChange",function(a,b,c){b!==c&&(b===P?R():c===P&&R(!0))}),w(h+"."+P,function(){R()})},getIframe:function(c,d){var e=c.src,f=b.st.iframe;a.each(f.patterns,function(){return e.indexOf(this.index)>-1?(this.id&&(e="string"==typeof this.id?e.substr(e.lastIndexOf(this.id)+this.id.length,e.length):this.id.call(this,e)),e=this.src.replace("%id%",e),!1):void 0});var g={};return f.srcAction&&(g[f.srcAction]=e),b._parseMarkup(d,g,c),b.updateStatus("ready"),d}}});var S=function(a){var c=b.items.length;return a>c-1?a-c:0>a?c+a:a},T=function(a,b,c){return a.replace(/%curr%/gi,b+1).replace(/%total%/gi,c)};a.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var c=b.st.gallery,e=".mfp-gallery";return b.direction=!0,c&&c.enabled?(f+=" mfp-gallery",w(m+e,function(){c.navigateByImgClick&&b.wrap.on("click"+e,".mfp-img",function(){return b.items.length>1?(b.next(),!1):void 0}),d.on("keydown"+e,function(a){37===a.keyCode?b.prev():39===a.keyCode&&b.next()})}),w("UpdateStatus"+e,function(a,c){c.text&&(c.text=T(c.text,b.currItem.index,b.items.length))}),w(l+e,function(a,d,e,f){var g=b.items.length;e.counter=g>1?T(c.tCounter,f.index,g):""}),w("BuildControls"+e,function(){if(b.items.length>1&&c.arrows&&!b.arrowLeft){var d=c.arrowMarkup,e=b.arrowLeft=a(d.replace(/%title%/gi,c.tPrev).replace(/%dir%/gi,"left")).addClass(s),f=b.arrowRight=a(d.replace(/%title%/gi,c.tNext).replace(/%dir%/gi,"right")).addClass(s);e.click(function(){b.prev()}),f.click(function(){b.next()}),b.container.append(e.add(f))}}),w(n+e,function(){b._preloadTimeout&&clearTimeout(b._preloadTimeout),b._preloadTimeout=setTimeout(function(){b.preloadNearbyImages(),b._preloadTimeout=null},16)}),void w(h+e,function(){d.off(e),b.wrap.off("click"+e),b.arrowRight=b.arrowLeft=null})):!1},next:function(){b.direction=!0,b.index=S(b.index+1),b.updateItemHTML()},prev:function(){b.direction=!1,b.index=S(b.index-1),b.updateItemHTML()},goTo:function(a){b.direction=a>=b.index,b.index=a,b.updateItemHTML()},preloadNearbyImages:function(){var a,c=b.st.gallery.preload,d=Math.min(c[0],b.items.length),e=Math.min(c[1],b.items.length);for(a=1;a<=(b.direction?e:d);a++)b._preloadItem(b.index+a);for(a=1;a<=(b.direction?d:e);a++)b._preloadItem(b.index-a)},_preloadItem:function(c){if(c=S(c),!b.items[c].preloaded){var d=b.items[c];d.parsed||(d=b.parseEl(c)),y("LazyLoad",d),"image"===d.type&&(d.img=a('<img class="mfp-img" />').on("load.mfploader",function(){d.hasSize=!0}).on("error.mfploader",function(){d.hasSize=!0,d.loadError=!0,y("LazyLoadError",d)}).attr("src",d.src)),d.preloaded=!0}}}});var U="retina";a.magnificPopup.registerModule(U,{options:{replaceSrc:function(a){return a.src.replace(/\.\w+$/,function(a){return"@2x"+a})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var a=b.st.retina,c=a.ratio;c=isNaN(c)?c():c,c>1&&(w("ImageHasSize."+U,function(a,b){b.img.css({"max-width":b.img[0].naturalWidth/c,width:"100%"})}),w("ElementParse."+U,function(b,d){d.src=a.replaceSrc(d,c)}))}}}}),A()});
640
641// Universal Tooltip
642class UniversalTooltip {
643    constructor(el, opts = {}) {
644        this.el         = el;
645        this.opts       = opts;
646        this.getContent = typeof opts.content === 'function'
647            ? () =>
647 opts.content(this.el)
648            : () => opts.content ?? el.dataset.tooltip ?? '';
649        this.trigger   = opts.trigger   ?? el.dataset.tooltipTrigger   ?? 'hover';
650        this.placement = opts.placement ?? el.dataset.tooltipPlacement ?? 'top';
651        this.autoHide  = opts.autoHide  ?? (+el.dataset.tooltipAutohide || null);
652        this.tip       = null;
653        this._show     = this.show.bind(this);
654        this._hide     = this.hide.bind(this);
655        this._docClick = this._onDocClick.bind(this);
656        this._init();
657    }
658
659    static attach(selectorOrList, opts = {}) {
660        let list;
661        if (typeof selectorOrList === 'string') {
662            list = document.querySelectorAll(selectorOrList);
663        } else if (selectorOrList instanceof NodeList || Array.isArray(selectorOrList)) {
664            list = selectorOrList;
665        } else {
666            list = [selectorOrList];
667        }
668        list.forEach(el => new UniversalTooltip(el, opts));
669    }
670    static initAll() { UniversalTooltip.attach('[data-tooltip]'); }
671
672    _init() {
673        if (this.trigger === 'manual') return; // caller drives show()/hide(); no auto-wiring
674        if (this.trigger === 'hover') {
675            this.el.addEventListener('mouseenter', this._show);
676            this.el.addEventListener('mouseleave', this._hide);
677        } else if (this.trigger === 'click') {
678            this.el.addEventListener('click', e => {
679                e.preventDefault();
680                this.toggle();
681            });
682            document.addEventListener('click', this._docClick);
683        }
684    }
685    _onDocClick(e) {
686        if (!this.el.contains(e.target) && this.tip && !this.tip.contains(e.target)) {
687            this.hide();
688        }
689    }
690
691    _create() {
692        if (this.tip) return;
693
694        const attrCont = this.el.dataset.tooltipContainer;
695        const inDialog = this.el.closest('dialog[open]');
696        this.container = this.opts.container
697            || (attrCont && document.querySelector(attrCont))
698            || inDialog
699            || document.body;
700
701        if (this.container !== document.body &&
702            getComputedStyle(this.container).position === 'static') {
703            this.container.style.position = 'relative';
704        }
705        if (inDialog) inDialog.style.overflow = 'visible';
706
707        const tip = document.createElement('div');
708        tip.className         = 'tooltip-container';
709        tip.dataset.placement = this.placement;
710
711        if (this.trigger !== 'hover') tip.classList.add('interactive');
712        if (this.opts.tipClass) tip.classList.add(this.opts.tipClass);
713
714        tip.innerHTML         = this.getContent();
715        tip.style.position    = 'absolute';
716        tip.style.zIndex      = '2147483647';
717
718        const arrow = document.createElement('div');
719        arrow.className = 'tooltip-arrow';
720        tip.appendChild(arrow);
721
722        this.container.appendChild(tip);
723        this.tip = tip;
724    }
725
726    _position() {
727        if (!this.tip) return;
728
729        const elemRect = this.el.getBoundingClientRect();
730        const margin   = 8;
731        const isBody   = this.container === document.body;
732        const scrollX  = isBody ? (window.scrollX || window.pageXOffset) : 0;
733        const scrollY  = isBody ? (window.scrollY || window.pageYOffset) : 0;
734
735        let left = elemRect.left + elemRect.width / 2 - this.tip.offsetWidth / 2 + scrollX;
736        let top;
737
738        if (this.placement === 'bottom') {
739            top = elemRect.bottom + margin + scrollY;
740        } else {
741            top = elemRect.top - this.tip.offsetHeight - margin + scrollY;
742        }
743
744        if (!isBody) {
745            const contRect = this.container.getBoundingClientRect();
746            left -= contRect.left;
747            top  -= contRect.top;
748        }
749
750        const maxW = isBody ? window.innerWidth : this.container.clientWidth;
751        left = Math.max(8, Math.min(left, maxW - this.tip.offsetWidth - 8));
752
753        this.tip.style.left = `${left}px`;
754        this.tip.style.top  = `${top}px`;
755
756        const arrow = this.tip.querySelector('.tooltip-arrow');
757        if (arrow) {
758            const contLeft  = isBody ? 0 : this.container.getBoundingClientRect().left;
759            const centerDoc = elemRect.left + elemRect.width / 2 + scrollX;
760            let arrowLeft   = centerDoc - contLeft - left;
761            const aw = 12;
762            arrowLeft = Math.max(aw / 2, Math.min(arrowLeft, this.tip.offsetWidth - aw / 2));
763            arrow.style.left = `${arrowLeft}px`;
764            arrow.style.transform = '';
765        }
766    }
767
768    show() {
769        this._create();
770        this._position();
771        requestAnimationFrame(() => this.tip.classList.add('show'));
772        if (this.autoHide) {
773            clearTimeout(this._hideTimeout);
774            this._hideTimeout = setTimeout(() => this.hide(), this.autoHide);
775        }
776    }
777
778    hide() {
779        if (!this.tip) return;
780        this.tip.classList.remove('show');
781        const end = () => {
782            if (this.tip) {
783                this.tip.remove();
784                this.tip = null;
785            }
786        };
787        this.tip.addEventListener('transitionend', end, {once: true});
788    }
789
790    toggle() {
791        (this.tip && this.tip.classList.contains('show')) ? this.hide() : this.show();
792    }
793
794    dispose() {
795        if (this.tip) this.tip.remove();
796        this.el.removeEventListener('mouseenter', this._show);
797        this.el.removeEventListener('mouseleave', this._hide);
798        this.el.removeEventListener('click', this.toggle);
799        document.removeEventListener('click', this._docClick);
800        this.tip = null;
801    }
802}
803
804document.addEventListener('DOMContentLoaded', () => {
805    UniversalTooltip.initAll();
806});
807
808// Universal Modal
809class UniversalModal {
810    constructor(element, options = {}) {
811        this.element = typeof element === 'string' ? document.querySelector(element) : element;
812        this.options = {
813            backdrop: options.backdrop !== undefined ? options.backdrop : true,
814            keyboard: options.keyboard !== undefined ? options.keyboard : true,
815            focus: options.focus !== undefined ? options.focus : true,
816            show: options.show !== undefined ? options.show : false
817        };
818
819        this.isShown = false;
820        this.backdrop = null;
821        this.scrollbarWidth = 0;
822
823        this._init();
824
825        if (this.options.show) {
826            this.show();
827        }
828    }
829
830    _init() {
831        const closeButtons = this.element.querySelectorAll('[data-dismiss="universal-modal"]');
832        closeButtons.forEach(btn => {
833            btn.addEventListener('click', (e) => {
834                e.preventDefault();
835                this.hide();
836            });
837        });
838
839        if (this.options.backdrop) {
840            this.element.addEventListener('click', (e) => {
841                if (e.target === this.element) {
842                    this.hide();
843                }
844            });
845        }
846
847        if (this.options.keyboard) {
848            document.addEventListener('keydown', (e) => {
849                if (e.key === 'Escape' && this.isShown) {
850                    this.hide();
851                }
852            });
853        }
854    }
855
856    show() {
857        if (this.isShown) return;
858
859        this.isShown = true;
860
861        this.scrollbarWidth = this._getScrollbarWidth();
862
863        if (this.scrollbarWidth > 0) {
864            document.body.style.paddingRight = this.scrollbarWidth + 'px';
865        }
866
867        document.body.classList.add('universal-modal-open');
868
869        if (this.options.backdrop) {
870            this._showBackdrop();
871        }
872
873        this.element.style.display = 'block';
874        this.element.setAttribute('aria-hidden', 'false');
875
876        setTimeout(() => {
877            this.element.classList.add('show');
878
879            if (this.options.focus) {
880                this.element.focus();
881            }
882        }, 10);
883
884        this._triggerEvent('show');
885    }
886
887    hide() {
888        if (!this.isShown) return;
889
890        this.isShown = false;
891
892        this.element.classList.remove('show');
893
894        setTimeout(() => {
895            this.element.style.display = 'none';
896            this.element.setAttribute('aria-hidden', 'true');
897
898            if (this.backdrop) {
899                this._hideBackdrop();
900            }
901
902            document.body.classList.remove('universal-modal-open');
903            document.body.style.paddingRight = '';
904
905            this._triggerEvent('hide');
906        }, 300);
907    }
908
909    toggle() {
910        this.isShown ? this.hide() : this.show();
911    }
912
913    _showBackdrop() {
914        this.backdrop = document.createElement('div');
915        this.backdrop.className = 'universal-modal-backdrop fade';
916        document.body.appendChild(this.backdrop);
917
918        setTimeout(() => {
919            this.backdrop.classList.add('show');
920        }, 10);
921    }
922
923    _hideBackdrop() {
924        if (!this.backdrop) return;
925
926        this.backdrop.classList.remove('show');
927
928        setTimeout(() => {
929            if (this.backdrop && this.backdrop.parentNode) {
930                this.backdrop.parentNode.removeChild(this.backdrop);
931            }
932            this.backdrop = null;
933        }, 300);
934    }
935
936    _getScrollbarWidth() {
937        const documentWidth = document.documentElement.clientWidth;
938        return Math.abs(window.innerWidth - documentWidth);
939    }
940
941    _triggerEvent(eventName) {
942        const event = new CustomEvent('universalModal.' + eventName, {
943            bubbles: true,
944            cancelable: true,
945            detail: { universalModal: this }
946        });
947        this.element.dispatchEvent(event);
948    }
949
950    static _jQueryInterface(config) {
951        return this.each(function() {
952            let data = this._universalModalInstance;
953            const _config = typeof config === 'object' && config;
954
955            if (!data) {
956                data = new UniversalModal(this, _config);
957                this._universalModalInstance = data;
958            }
959
960            if (typeof config === 'string') {
961                if (typeof data[config] === 'undefined') {
962                    throw new TypeError(`No method named "${config}"`);
963                }
964                data[config]();
965            }
966        });
967    }
968}
969
970document.addEventListener('click', function(e) {
971    const trigger = e.target.closest('[data-toggle="universal-modal"]');
972    if (!trigger) return;
973
974    e.preventDefault();
975
976    const targetSelector = trigger.getAttribute('data-target') || trigger.getAttribute('href');
977    if (!targetSelector) return;
978
979    const targetElement = document.querySelector(targetSelector);
980    if (!targetElement) return;
981
982    let universalModal = targetElement._universalModalInstance;
983    if (!universalModal) {
984        universalModal = new UniversalModal(targetElement);
985        targetElement._universalModalInstance = universalModal;
986    }
987
988    universalModal.show();
989});
990
991window.UniversalModal = UniversalModal;
992
993const TOKEN_TTL_REFRESH_MS = 60_000;
994const TOKEN_STORE_KEY_PREFIX = 'st-cache:';
995
996/*
997 * Ceiling on a single /search-token mint. See TokenManager._mint for why an
998 * unbounded mint is worse than a slow one.
999 *
1000 * 1000ms is deliberately generous against what the endpoint actually costs: it
1001 * signs a token and returns, and the D1 Worker's equivalent was measured at
1002 * 272ms end to end from a residential connection. A mint that has not answered
1003 * in a full second is not slow, it is broken, and the caller is better served
1004 * by an error it can fall back on than by a wait it cannot interrupt.
1005 *
1006 * This is not per-search overhead. Tokens are cached in memory, in
1007 * sessionStorage, and shared across tabs over BroadcastChannel, so a mint
1008 * happens roughly once per host per session rather than once per query. It does
1009 * widen the theoretical worst case for a caller that walks several hosts in
1010 * turn - each host it reaches for the first time can add up to this on top of
1011 * its own request budget - but that only bites when every backend is
1012 * simultaneously unreachable, which is the case where a bounded failure is
1013 * exactly what you want.
1014 */
1015const TOKEN_MINT_TIMEOUT_MS = 1000;
1016const IS_LOCAL = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
1017const PRIMARY_BASE = IS_LOCAL
1018    ? 'http://localhost:1597/aznude-fea6a/us-central1/main/app'
1019    : 'https://main-aq7es5tiuq-uc.a.run.app/app';
1020
1021const D1_BASE = IS_LOCAL
1022    ? 'http://127.0.0.1:8787'
1023    : 'https://search-aznude.aznude.workers.dev';
1024
1025/*
1026 * Base for the Neon-only Cloud Run service (`mainNeon`, declared in
1027 * functions/index.js). Declared alongside PRIMARY_BASE and D1_BASE so it is a
1028 * page global in the same way they are.
1029 *
1030 * NOTHING READS THIS YET, deliberately. It is here so the constant lands with
1031 * the service that backs it, in one reviewable change, rather than appearing
1032 * halfway through the follow-up that makes SearchPage.jsx fall back
1033 * Neon -> Turso -> D1. Until that lands, search behaviour is unchanged.
1034 *
1035 * VERIFY THE HOST before the first thing depends on it. The production value
1036 * below is derived from the naming Cloud Run already uses across this project
1037 * (`main` -> main-aq7es5tiuq-uc.a.run.app, and the same shape for every other
1038 * function), so it should be right - but it is a prediction until
1039 * `firebase deploy --only functions:mainNeon` has actually printed a URL. A
1040 * wrong host here would not error loudly; it would make the Neon tier fail
1041 * every request and silently serve everything from the tier behind it, which
1042 * looks like "Neon is slow" rather than "Neon was never reached".
1043 *
1044 * SCOPE: results only, and that is about WHICH SERVICE this constant points
1045 * at, not which engine answers. It used to say that /exp/search-suggestions
1046 * would run against Turso whatever host served it. That stopped being true on
1047 * 2026-09-16: routes/search.js now serves suggestions from Neon first and
1048 * falls back to Turso, on `main` and `mainNeon` alike, because they mount the
1049 * same Express app. So suggestions are already on Neon and do not need this
1050 * base to get there.
1051 */
1052const NEON_BASE = IS_LOCAL
1053    ? 'http://localhost:1597/aznude-fea6a/us-central1/mainNeon/app'
1054    : 'https://mainneon-aq7es5tiuq-uc.a.run.app/app';
1055
1056/*
1057 * Cacheable read traffic goes through the site's own hostname, where the Go
1058 * proxy behind Cloudflare forwards /app/* to Cloud Run and the edge can answer
1059 * repeats without waking it. Everything with a session in it — auth, votes,
1060 * favourites — stays on PRIMARY_BASE: az_session is scoped to the Cloud Run
1061 * host, that shared host IS the cross-domain login, and the proxy strips
1062 * cookies in both directions by design.
1063 *
1064 * Tokens are still minted on PRIMARY_BASE. A token response is one client's
1065 * token and sid and says no-store, so sending it through the edge would cache
1066 * nothing (it used to set an az_sid cookie as well; that is gone, AZ-103).
1067 * The proxy rewrites Host to the run.app hostname of whichever service it
1068 * forwards to, so a token's `aud` has to name that service. For the trending
1069 * strip that is `main`, so the PRIMARY_BASE token is right on both paths. The
1070 * proxy sends /exp/search-suggestions to mainNeon instead (AZ-69), so the
1071 * suggestions edge tier mints on NEON_BASE; see SUGGEST_TIERS.
1072 *
1073 * EDGE_READ_HOSTS is the frontend's own switch. Empty on purpose until the
1074 * pilot host has been proven end to end (x-served-from on every hostname, a
1075 * real cache HIT on a route we chose); a host is added here only after that,
1076 * one at a time. An unlisted host — beta.*, users.*, a freshly rotated
1077 * domain — keeps today's behaviour without anyone having to remember it.
1078 */
1079const EDGE_READ_HOSTS = new Set(['azfans.com', 'www.azfans.com']);
1080
1081let edgeReadDisabled = false;
1082
1083function edgeReadBase() {
1084    if (IS_LOCAL || edgeReadDisabled) return PRIMARY_BASE;
1085    return EDGE_READ_HOSTS.has(location.hostname) ? '/app' : PRIMARY_BASE;
1086}
1087
1088/*
1089 * One failure disables the edge path for the rest of the page view. A proxy
1090 * or rule that is wrong fails the same way for every request, so retrying
1091 * per call would only double every failure.
1092 */
1093function disableEdgeRead(err) {
1094    if (edgeReadDisabled) return;
1095    edgeReadDisabled = true;
1096    console.warn('[edge] falling back to origin for this page view:', err && err.message);
1097}
1098
1099/*
1100 * Run attempt(base) against the edge base first and the origin second. On
1101 * the origin there is nothing left to fall back to, so its failure propagates
1102 * exactly as it did before this path existed.
1103 */
1104async function withEdgeFallback(attempt) {
1105    const base = edgeReadBase();
1106    try {
1107        return await attempt(base);
1108    } catch (err) {
1109        // A caller-cancelled request is not an edge failure. Without this guard
1110        // an AbortError would disable the edge path for the rest of the page
1111        // view (edgeReadDisabled is shared by every edge read) and waste an
1112        // origin retry on the already-aborted signal.
1113        if (err && err.name === 'AbortError') throw err;
1114        // A token the origin rejected is not an edge failure either: disabling
1115        // would take every edge read off the edge for one expired token, and the
1116        // origin retry would be refused the same way. The trending strip never
1117        // throws AUTH_401 today; this is here for the next caller that does.
1118        // Suggestions do not come through here -- they walk their own tiers
1119        // and make the same call there (see suggestFirstTier).
1120        if (err && String(err.message).startsWith('AUTH_401')) throw err;
1121        if (base === PRIMARY_BASE) throw err;
1122        disableEdgeRead(err);
1123        return await attempt(PRIMARY_BASE);
1124    }
1125}
1126
1127/*
1128 * The trending strip's request, against whichever base it is handed. Kept
1129 * out of the closure that calls it so the request it builds can be tested
1130 * (test/frontend/edge-read.test.js) — the token travels in headers, never in
1131 * the URL, and a non-OK answer throws so withEdgeFallback can act on it.
1132 */
1133async function fetchTrendingFrom(base, { token, sid, gender, countryCode, sexualityParam }) {
1134    const resp = await fetch(`${base}/most-searched-queries?gender=${gender}&limit=12&period=past24hours&country=${countryCode}${sexualityParam}`, {
1135        headers: {
1136            'x-st': token,
1137            'x-sid': sid
1138        },
1139        credentials: IS_LOCAL ? 'omit' : 'include'
1140    });
1141    if (!resp.ok) throw new Error("Failed to load trending");
1142    return resp.json();
1143}
1144
1145/*
1146 * The search-suggestions request, against whichever base it is handed. Same
1147 * shape and reasoning as fetchTrendingFrom: kept out of its closure so the
1148 * request it builds is testable (test/frontend/edge-read.test.js), token in
1149 * headers never the URL, non-OK throws so the tier walk can move on. The caller
1150 * builds the path (it depends on varValues), so only base + creds live here.
1151 *
1152 * No User-Agent is set here, and none may be. The search-token gate binds the
1153 * token to a hash of the raw UA (`uah`), so the browser's own header has to
1154 * reach it byte for byte; the Go proxy is verified to forward it unchanged.
1155 *
1156 * A 401 carries the gate's reason (`aud_mismatch`, `expired`, ...) in the
1157 * message. The gate does not log its 401 body and Cloud Run shows only the
1158 * status, so the walk's failure line is the one place a wrong-audience token
1159 * is distinguishable from an expired one. It still starts with AUTH_401.
1160 */
1161async function fetchSuggestionsFrom(base, path, { token, sid, signal }) {
1162    const resp = await fetch(base.replace(/\/$/, '') + path, {
1163        method: 'GET',
1164        signal,
1165        credentials: IS_LOCAL ? 'omit' : 'include',
1166        headers: {
1167            'x-st': token,
1168            'x-sid': sid
1169        }
1170    });
1171    if (resp.status === 401) {
1172        let reason = '';
1173        try { reason = (await resp.json())?.error || ''; } catch {}
1174        throw new Error(reason ? 'AUTH_401 ' + reason : 'AUTH_401');
1175    }
1176    if (!resp.ok) throw new Error('HTTP_' + resp.status);
1177    return resp.json();
1178}
1179
1180/*
1181 * The first tier of the suggestions walk (SUGGEST_TIERS). On an edge host it
1182 * is the edge, and the edge REPLACES neon rather than going in front of it.
1183 * The site's own /app is the Go proxy, which sends this one route to mainNeon
1184 * (AZ-69), so the edge is Neon behind the Cloudflare cache:
1185 *
1186 *   edge  /app -> CF cache; miss -> proxy -> mainNeon    Neon
1187 *   turso PRIMARY_BASE                                   Turso
1188 *   d1    D1_BASE                                        D1
1189 *
1190 * A fourth tier would ask Neon twice. Falling back from the edge to
1191 * PRIMARY_BASE, as withEdgeFallback does for the trending strip, would reach
1192 * Turso before Neon was tried.
1193 *
1194 * THE EDGE'S TOKEN IS MINTED ON NEON_BASE, NOT ON THE BASE IT CALLS. The gate
1195 * checks the token's `aud` against the Host it is served under, and the proxy
1196 * rewrites Host to mainNeon's. A PRIMARY_BASE token there is `401
1197 * aud_mismatch`, measured through azfans after AZ-69 deployed. Nothing would
1198 * look broken: the tier would cool down and Turso would answer every keystroke.
1199 *
1200 * Once the edge is off for the page view, because the walk or the trending
1201 * strip saw the proxy fail, this is plain neon again. Kept out of the walk's
1202 * closure so the choice, and above all the token base, is tested
1203 * (test/frontend/edge-read.test.js).
1204 */
1205function suggestFirstTier() {
1206    const neon = typeof NEON_BASE !== 'undefined' ? NEON_BASE : null;
1207    const base = edgeReadBase();
1208    return neon && base !== PRIMARY_BASE
1209        ? { name: 'edge', base, tokenBase: neon, timeout: 1200 }
1210        : { name: 'neon', base: neon, timeout: 1200 };
1211}
1212
1213window.TokenManager = class {
1214    constructor() {
1215        this.cache = new Map();
1216        this.inflight = new Map();
1217        this.bc = null;
1218        try { this.bc = new BroadcastChannel('search-token'); } catch {}
1219        if (this.bc) {
1220            this.bc.onmessage = (e) => {
1221                const { host, token, exp, sid, issuer } = e.data || {};
1222                if (!host || !token || !exp || !issuer) return;
1223                this.cache.set(host, { token, exp, sid, issuer });
1224                try { sessionStorage.setItem(TOKEN_STORE_KEY_PREFIX + host, JSON.stringify({ token, exp, sid, issuer })); } catch {}
1225            };
1226        }
1227        document.addEventListener('visibilitychange', () => {
1228            if (document.visibilityState === 'visible') {
1229                for (const host of this.cache.keys()) {
1230                    this.get(host, false).catch(()=>{});
1231                }
1232            }
1233        });
1234    }
1235
1236    _loadFromSession(host) {
1237        try {
1238            const raw = sessionStorage.getItem(TOKEN_STORE_KEY_PREFIX + host);
1239            if (!raw) return null;
1240            const parsed = JSON.parse(raw);
1241
1242            if (parsed.issuer && parsed.issuer !== host) {
1243                sessionStorage.removeItem(TOKEN_STORE_KEY_PREFIX + host);
1244                return null;
1245            }
1246
1247            if (typeof parsed.exp !== 'number' || !parsed.token) return null;
1248            return parsed;
1249        } catch { return null; }
1250    }
1251
1252    _shouldRefresh(item, now) {
1253        return !item || now >= (item.exp - TOKEN_TTL_REFRESH_MS);
1254    }
1255
1256    /*
1257     * Mint a token for `host`, bounded by TOKEN_MINT_TIMEOUT_MS.
1258     *
1259     * THE TIMEOUT IS LOAD-BEARING, not defensive tidying. Without it this was
1260     * an unbounded fetch, and the failure it produced was worse than a slow
1261     * search:
1262     *
1263     *   1. A host that accepts the connection but never answers leaves this
1264     *      promise pending forever. Callers await the token BEFORE any request
1265     *      timeout of their own exists, so no caller budget can rescue it - the
1266     *      hang happens strictly earlier than anything that could time it out.
1267     *   2. `inflight` then makes it contagious. Caching the pending promise is
1268     *      correct and dedupes concurrent mints, but `finally` only clears the
1269     *      entry once the promise SETTLES. One that never settles is never
1270     *      cleared, so every later search for that host joins the same dead
1271     *      promise. Reproduced by Vlad on #34: three searches in a row hung and
1272     *      the database was never reached once.
1273     *
1274     * So one fix closes both. Aborting makes the promise reject, `finally`
1275     * runs, the entry clears, and the next search mints fresh. `inflight`
1276     * itself needed no change - it was only ever pathological because nothing
1277     * guaranteed settlement.
1278     *
1279     * The timer spans the body read as well as the headers. Clearing it once
1280     * the response object arrives would leave a stalled body unbounded, which
1281     * is the same bug one step further in.
1282     */
1283    async _mint(host) {
1284        if (this.inflight.has(host)) return this.inflight.get(host);
1285
1286        const p = (async () => {
1287            const base = host.replace(/\/$/, '');
1288            const url  = `${base}/search-token`;
1289
1290            const controller = new AbortController();
1291            const timer = setTimeout(() => controller.abort(), TOKEN_MINT_TIMEOUT_MS);
1292            try {
1293                const resp = await fetch(url, {
1294                    credentials: IS_LOCAL ? 'omit' : 'include',
1295                    signal: controller.signal,
1296                });
1297                if (!resp.ok) throw new Error(`token_mint_failed:${resp.status}`);
1298                const { token, exp, sid } = await resp.json();
1299
1300                const pack = { token, exp, sid, issuer: host };
1301                this.cache.set(host, pack);
1302                try { sessionStorage.setItem(TOKEN_STORE_KEY_PREFIX + host, JSON.stringify(pack)); } catch {}
1303                if (this.bc) { this.bc.postMessage({ host, token, exp, sid, issuer: host }); }
1304                return pack;
1305            } finally {
1306                clearTimeout(timer);
1307            }
1308        })();
1309
1310        this.inflight.set(host, p);
1311        try { return await p; }
1312        finally { this.inflight.delete(host); }
1313    }
1314
1315    /*
1316     * Drop a token the server has rejected.
1317     *
1318     * Freshness is decided by `_shouldRefresh` against the browser's own
1319     * Date.now(), so a client whose clock runs behind believes a long-dead
1320     * token is still valid and never re-mints. On 1 Sep 2026 one client
1321     * replayed the same expired token across every token-gated route for a
1322     * whole day without a single /search-token call, and had no way out: a
1323     * 401 left the cache untouched, so the next request replayed it again.
1324     *
1325     * Callers invoke this on a 401 to break that loop. The next get() misses
1326     * both caches and mints fresh, which is valid regardless of the clock.
1327     */
1328    invalidate(host) {
1329        this.cache.delete(host);
1330        try { sessionStorage.removeItem(TOKEN_STORE_KEY_PREFIX + host); } catch {}
1331    }
1332
1333    async get(host, forcePossible = true) {
1334        const now = Date.now();
1335
1336        let item = this.cache.get(host);
1337        if (!item) {
1338            const saved = this._loadFromSession(host);
1339            if (saved) {
1340                this.cache.set(host, saved);
1341                item = saved;
1342            }
1343        }
1344
1345        if (!item || this._shouldRefresh(item, now)) {
1346            return this._mint(host);
1347        }
1348
1349        if (forcePossible && (item.exp - now) < TOKEN_TTL_REFRESH_MS) {
1350            this._mint(host).catch(()=>{});
1351        }
1352
1353        return item;
1354    }
1355}
1356
1357const tokenManager = new TokenManager();
1358
1359async function getSearchTokenFor(base) {
1360    return tokenManager.get(base);
1361}
1362
1363/*
1364 * Exposed for callers that get a 401 from a token-gated route: purge, then
1365 * re-mint. See TokenManager.invalidate.
1366 */
1367function invalidateSearchTokenFor(base) {
1368    tokenManager.invalidate(base);
1369}
1370
1371// Playlists
1372let OriPicNum = 0;
1373let CurDocument = 0;
1374let CurPicNum = 0;
1375let CurUrl = "";
1376
1377function preLoadPicture(pic) {
1378    heavyImage = new Image();
1379    heavyImage.src = CurUrl + pic + ".jpg";
1380}
1381
1382function getNextThumbNum(CurNum) {
1383    thumbs = new Array(1, 2, 3, 4, 5, 6);
1384    for (i = 0; i < thumbs.length; i++) {
1385        if (thumbs[i] > CurNum) {
1386            return thumbs[i];
1387        }
1388    }
1389    return thumbs[0];
1390}
1391
1392function nextThumbnail(idDoc) {
1393    if (CurDocument != 0 && idDoc == CurDocument) {
1394        document.getElementById("pic_" + CurDocument).src = CurUrl + CurPicNum + ".jpg";
1395        nextthumb = getNextThumbNum(CurPicNum);
1396        preLoadPicture(nextthumb);
1397        CurPicNum = nextthumb;
1398        setTimeout("nextThumbnail(" + CurDocument + ")", 1000);
1399    }
1400}
1401
1402function stopThumbSlide() {
1403    TmpDoc = CurDocument;
1404    CurDocument = 0;
1405    document.getElementById("pic_" + TmpDoc).src = CurUrl + OriPicNum + ".jpg";
1406}
1407
1408function startThumbSlide(IdDoc, CurNum, Url) {
1409    if (CurDocument == 0) {
1410        OriPicNum = CurNum;
1411        CurDocument = IdDoc;
1412        CurUrl = Url;
1413        nextthumb = getNextThumbNum(CurNum);
1414        preLoadPicture(nextthumb);
1415        CurPicNum = nextthumb;
1416        setTimeout("nextThumbnail(" + CurDocument + ")", 20);
1417    }
1418}
1419
1420
1421// Single movie and maybe for other pages as well
1422$('.encolsed-magnific-popup-wrap-up').each(function() {
1423    var $container = $(this);
1424    var $website_url = '';
1425    var $imageLinks = $container.find('.media-list-item > a');
1426    var items = [];
1427    $imageLinks.each(function() {
1428        var $item = $(this);
1429        var type = 'image';
1430        title = $item.children('a').attr('lightbox');
1431        if ($item.children('a').has('a[data-type="video"]')) {
1432            type = 'iframe';
1433
1434            $vid_src = $item.children('div').children('a').attr('eid');
1435            src = $website_url + $vid_src;
1436        } else {
1437            src = $item.children('a').attr('href');
1438        }
1439        var magItem = {
1440            src: src,
1441            type: type,
1442            title: title
1443        };
1444        if (magItem.src != '' && magItem.src.toLowerCase().indexOf(`view/${varValues.celeb_names.toLowerCase().slice(0, -1)}`) < 0 && magItem.src.toLowerCase().indexOf("/browse/") < 0) {
1445            items.push(magItem);
1446        }
1447    });
1448
1449    $imageLinks.magnificPopup({
1450        mainClass: 'mfp-fade',
1451        items: items,
1452        gallery: {
1453            enabled: true,
1454            tPrev: $(this).data('prev-text'),
1455            tNext: $(this).data('next-text')
1456        },
1457        type: 'image',
1458        callbacks: {
1459            beforeOpen: function() {
1460                var index = $imageLinks.index(this.st.el);
1461                if (-1 !== index) {
1462                    this.goTo(index);
1463                }
1464            }
1465        }
1466    });
1467});
1468
1469// Browse images and maybe for other pages as well
1470$('.enclosed-magnific-popup-wrap-up2').each(function() {
1471    var $container = $(this);
1472    var $imageLinks = $container.find('.media-list-item:not(.exo-item) a[lightbox]'); // even though it's story-image this actually targets all
1473    var items = [];
1474    $imageLinks.each(function() {
1475        var $item = $(this);
1476
1477        var type = 'image';
1478        title = $item.attr('lightbox');
1479        src = $item.attr('href');
1480        var magItem = {
1481            src: src,
1482            type: type,
1483            title: title
1484        };
1485        items.push(magItem);
1486    });
1487    $imageLinks.magnificPopup({
1488        mainClass: 'mfp-fade',
1489        items: items,
1490        gallery: {
1491            enabled: true,
1492            tPrev: $(this).data('prev-text'),
1493            tNext: $(this).data('next-text')
1494        },
1495        type: 'image',
1496        callbacks: {
1497            beforeOpen: function() {
1498                var index = $imageLinks.index(this.st.el);
1499                if (-1 !== index) {
1500                    this.goTo(index);
1501                }
1502            }
1503        }
1504    });
1505});
1506
1507function userHasAdBlockerOn() {
1508    return new Promise((resolve, reject) => {
1509        fetch('https://googleads.g.doubleclick.net/pagead/id', {
1510            method: "HEAD",
1511            mode: "no-cors",
1512            cache: "no-store",
1513        })
1514            .then(() => resolve(false))
1515            .catch(() => resolve(true));
1516    })
1517}
1518
1519// New Navbar
1520document.addEventListener("DOMContentLoaded", function() {
1521    // **Hamburger Menu (Vanilla JS)**
1522    const hamburgerDropdown = document.querySelector('.hamburger-dropdown');
1523    const hamburgerOverlay = document.querySelector('.hamburger-dropdown-overlay');
1524    const hamburgerToggler = document.getElementById('hamburger-menu');
1525
1526    function closeHamburgerMenu() {
1527        hamburgerToggler.setAttribute('data-nav-status', 'closed');
1528        hamburgerDropdown.classList.remove('hamburger-dropdown--show');
1529        hamburgerOverlay.classList.remove('hamburger-dropdown-overlay--show');
1530        setTimeout(() => {
1531            hamburgerDropdown.style.display = 'none';
1532            hamburgerOverlay.style.display = 'none';
1533            document.body.style.overflow = 'auto';
1534        }, window.screen.width < 1024 ? 100 : 0);
1535    }
1536
1537    function openHamburgerMenu() {
1538        hamburgerToggler.setAttribute('data-nav-status', 'open');
1539        hamburgerDropdown.style.display = 'block';
1540        hamburgerOverlay.style.display = 'block';
1541        setTimeout(() => {
1542            hamburgerDropdown.classList.add('hamburger-dropdown--show');
1543            hamburgerOverlay.classList.add('hamburger-dropdown-overlay--show');
1544            if (window.innerWidth < 1024) document.body.style.overflow = 'hidden';
1545        }, 50);
1546        hamburgerDropdown.querySelectorAll('.hamburger-dropdown__menu-item')[1]?.focus();
1547    }
1548
1549    hamburgerToggler.addEventListener('click', () => {
1550        hamburgerToggler.getAttribute('data-nav-status') === 'closed' ? openHamburgerMenu() : closeHamburgerMenu();
1551    });
1552
1553    document.querySelector('.hamburger-dropdown__close')?.addEventListener('click', (event) => {
1554        event.stopPropagation();
1555        closeHamburgerMenu();
1556    });
1557
1558    document.addEventListener('keydown', (event) => {
1559        if (event.target === hamburgerToggler && event.key === 'Enter') {
1560            hamburgerToggler.click();
1561        }
1562    });
1563
1564    document.addEventListener('click', (event) => {
1565        if (!event.target.closest('#hamburger-menu') &&
1566            !event.target.closest('.hamburger-dropdown') &&
1567            hamburgerDropdown.classList.contains('hamburger-dropdown--show')) {
1568            closeHamburgerMenu();
1569        }
1570    });
1571
1572    hamburgerOverlay.addEventListener('click', closeHamburgerMenu);
1573
1574    let resizeTimeout;
1575    window.addEventListener('resize', () => {
1576        clearTimeout(resizeTimeout);
1577        resizeTimeout = setTimeout(() => {
1578            if (window.innerWidth > 1024 && hamburgerDropdown.classList.contains('hamburger-dropdown--show')) {
1579                closeHamburgerMenu();
1580            }
1581        }, 100);
1582    });
1583
1584    // **Mega Menus Positioning**
1585    const subNav = document.querySelector('.sub-nav');
1586    function positionMegaMenu(megaMenu) {
1587        const subNavRect = subNav.getBoundingClientRect();
1588        megaMenu.style.width = '100%';
1589        megaMenu.style.left = `-${subNavRect.left}px`;
1590        megaMenu.style.top = `${subNavRect.bottom}px`;
1591    }
1592
1593    // **Generalized Mega Menus**
1594    const megaMenuParents = document.querySelectorAll('[data-mega-menu]');
1595    megaMenuParents.forEach(parent => {
1596        const targetName = parent.getAttribute('data-mega-menu');
1597        const megaMenu = document.querySelector(`[data-mega-menu-target="${targetName}"]`);
1598        let showTimer, hideTimer, isOverParent = false, isOverMenu = false;
1599
1600        if (megaMenu) {
1601            parent.addEventListener('mouseenter', () => {
1602                clearTimeout(hideTimer);
1603                isOverParent = true;
1604                parent.classList.add('hover');
1605                showTimer = setTimeout(() => {
1606                    megaMenu.style.display = 'block';
1607                    positionMegaMenu(megaMenu);
1608                    megaMenu.style.opacity = '1';
1609                }, 250);
1610            });
1611
1612            parent.addEventListener('mouseleave', () => {
1613                clearTimeout(showTimer);
1614                isOverParent = false;
1615                parent.classList.remove('hover');
1616                hideTimer = setTimeout(() => {
1617                    if (!isOverMenu) {
1618                        megaMenu.style.opacity = '0';
1619                        setTimeout(() => megaMenu.style.display = 'none', 250);
1620                    }
1621                }, 250);
1622            });
1623
1624            megaMenu.addEventListener('mouseenter', () => {
1625                clearTimeout(hideTimer);
1626                isOverMenu = true;
1627                parent.classList.add('hover');
1628            });
1629
1630            megaMenu.addEventListener('mouseleave', () => {
1631                clearTimeout(hideTimer);
1632                isOverMenu = false;
1633                parent.classList.remove('hover');
1634                hideTimer = setTimeout(() => {
1635                    if (!isOverParent) {
1636                        megaMenu.style.opacity = '0';
1637                        setTimeout(() => megaMenu.style.display = 'none', 250);
1638                    }
1639                }, 250);
1640            });
1641        }
1642    });
1643
1644    window.addEventListener('resize', () => {
1645        document.querySelectorAll('[data-mega-menu-target]').forEach(megaMenu => {
1646            if (megaMenu.style.display === 'block') positionMegaMenu(megaMenu);
1647        });
1648    });
1649
1650    // **Categories Rendering**
1651    const megaMenuContainers = document.querySelectorAll('.mega-menu-categories');
1652    if(varValues.navbarCategories && megaMenuContainers){
1653        const fansCategories = {
1654            "Straight": [
1655                {category:"Explicit", video_count:4031},
1656                {category:"Masturbation", video_count:1503},
1657                {category:"Blowjob", video_count:793},
1658                {category:"Lesbian", video_count:204},
1659                {category:"Anal", video_count:349},
1660                {category:"Topless", video_count:1234},
1661                {category:"Thong", video_count:850},
1662                {category:"Bikini", video_count:869},
1663                {category:"Underwear", video_count:78},
1664                {category:"Cumshot", video_count:251},
1665                {category:"Bush", video_count:49},
1666                {category:"Fetish", video_count:67}
1667            ],
1668            "Gay": [
1669                {category:"Penis", video_count:366},
1670                {category:"Shirtless", video_count:131},
1671                {category:"Anal", video_count:25},
1672                {category:"Masturbation", video_count:1},
1673                {category:"Blowjob", video_count:14},
1674                {category:"Cumshot", video_count:19},
1675                {category:"Bulge", video_count:35},
1676                {category:"Threesome", video_count:1},
1677                {category:"Butt", video_count:54},
1678                {category:"Muscular", video_count:1},
1679                {category:"Handjob", video_count:8},
1680                {category:"Bareback", video_count:5}
1681            ],
1682            "Trans": [
1683                {category:"Explicit", video_count:2},
1684                {category:"Penis", video_count:209},
1685                {category:"Anal", video_count:6},
1686                {category:"Blowjob", video_count:3},
1687                {category:"Boobs", video_count:3},
1688                {category:"Masturbation", video_count:3},
1689                {category:"Cumshot", video_count:2},
1690                {category:"Ladyboy", video_count:2},
1691                {category:"Butt", video_count:15},
1692                {category:"Dildo", video_count:10},
1693                {category:"Handjob", video_count:4},
1694                {category:"Lingerie", video_count:14}
1695            ]
1696        }
1697
1698        if(localStorage.getItem('sexuality')) varValues.navbarCategories = fansCategories[localStorage.getItem('sexuality')];
1699
1700        megaMenuContainers.forEach(container => {
1701            const html = varValues.navbarCategories.map(category => `
1702                <div class="mega-menu-categories-item">
1703                    <a href="${localStorage.getItem('sexuality')?.toLowerCase() || ''}/tags/vids/${category.category.toLowerCase()}/1.html" aria-label="View ${category.category} category with ${category.video_count} videos">
1704                        <img class="mega-menu-categories-item-img" src="${varValues.navbarCategoriesImageUrlPrefix}${localStorage.getItem('sexuality')?.toLowerCase() || ''}/${category.category.toLowerCase()}.jpg" alt="${category.category}" loading="lazy">
1705                        <div class="text-overlay">
1706                            <span class="category-name">${category.category}</span>
1707                            <span class="video-count">${category.video_count} videos</span>
1708                        </div>
1709                    </a>
1710                </div>
1711            `).join('');
1712
1713            container.innerHTML = html;
1714        });
1715    }
1716
1717    const categoriesToggle = document.querySelector('.categories-toggle');
1718    const categoriesDropdown = document.querySelector('.categories-dropdown-content');
1719    const dropdownArrow = categoriesToggle.querySelector('.dropdown-arrow');
1720    const categoriesContent = document.createElement('div');
1721    categoriesContent.className = 'mega-menu-categories';
1722    categoriesDropdown.appendChild(categoriesContent);
1723    categoriesDropdown.classList.add('open');
1724    dropdownArrow.classList.add('rotated');
1725    categoriesToggle.addEventListener('click', e => {
1726        e.preventDefault();
1727        const isOpen = categoriesDropdown.classList.contains('open');
1728        dropdownArrow.classList.toggle('rotated', !isOpen);
1729        categoriesDropdown.classList.toggle('open');
1730    });
1731
1732    // **Scrollbars**
1733    const navMenu = document.querySelector('.sub-nav-menu');
1734    if (navMenu) {
1735        navMenu.style.overflowX = 'auto';
1736        const scrollbar = document.createElement('div');
1737        scrollbar.className = 'custom-scrollbar';
1738        scrollbar.innerHTML = '<div class="scrollbar-track"><div class="scrollbar-thumb"></div></div>';
1739        navMenu.parentNode.insertBefore(scrollbar, navMenu.nextSibling);
1740        const thumb = scrollbar.querySelector('.scrollbar-thumb');
1741
1742        function updateThumb() {
1743            const contentWidth = navMenu.scrollWidth, containerWidth = navMenu.clientWidth;
1744            if (contentWidth <= containerWidth) return scrollbar.style.display = 'none';
1745            scrollbar.style.display = 'block';
1746            const ratio = containerWidth / contentWidth;
1747            thumb.style.width = `${ratio * containerWidth}px`;
1748            const scrollLeft = navMenu.scrollLeft, maxScroll = contentWidth - containerWidth;
1749            thumb.style.left = `${maxScroll ? (scrollLeft / maxScroll) * (containerWidth - thumb.clientWidth) : 0}px`;
1750        }
1751        updateThumb();
1752
1753        navMenu.addEventListener('scroll', updateThumb, { passive: true });
1754        window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(updateThumb, 100); });
1755
1756        let isDragging = false, dragStartX, thumbStartLeft;
1757        thumb.addEventListener('mousedown', e => {
1758            isDragging = true;
1759            dragStartX = e.clientX;
1760            thumbStartLeft = parseInt(thumb.style.left || '0', 10);
1761            document.body.style.userSelect = 'none';
1762        });
1763        document.addEventListener('mousemove', e => {
1764            if (!isDragging) return;
1765            const dx = e.clientX - dragStartX, newThumbLeft = thumbStartLeft + dx;
1766            const maxThumbLeft = navMenu.clientWidth - thumb.clientWidth;
1767            thumb.style.left = `${Math.max(0, Math.min(newThumbLeft, maxThumbLeft))}px`;
1768            navMenu.scrollLeft = (parseInt(thumb.style.left) / maxThumbLeft) * (navMenu.scrollWidth - navMenu.clientWidth);
1769        });
1770        document.addEventListener('mouseup', () => {
1771            if (isDragging) { isDragging = false; document.body.style.userSelect = ''; }
1772        });
1773    }
1774
1775    const hamburgerMenu = document.querySelector('.hamburger-dropdown__menu');
1776    const hamburgerMenuContainer = document.querySelector('.hamburger-dropdown__menu-container');
1777    if (hamburgerMenu && hamburgerMenuContainer) {
1778        const scrollbarVertical = document.createElement('div');
1779        scrollbarVertical.className = 'custom-scrollbar-vertical';
1780        scrollbarVertical.innerHTML = '<div class="scrollbar-track-vertical"><div class="scrollbar-thumb-vertical"></div></div>';
1781        hamburgerMenuContainer.appendChild(scrollbarVertical);
1782        const thumbVertical = scrollbarVertical.querySelector('.scrollbar-thumb-vertical');
1783
1784        function updateThumbVertical() {
1785            const contentHeight = hamburgerMenu.scrollHeight, containerHeight = hamburgerMenu.clientHeight;
1786            if (contentHeight <= containerHeight) return scrollbarVertical.style.display = 'none';
1787            scrollbarVertical.style.display = 'block';
1788            const ratio = containerHeight / contentHeight;
1789            thumbVertical.style.height = `${ratio * containerHeight}px`;
1790            const scrollTop = hamburgerMenu.scrollTop, maxScroll = contentHeight - containerHeight;
1791            thumbVertical.style.top = `${maxScroll ? (scrollTop / maxScroll) * (containerHeight - thumbVertical.clientHeight) : 0}px`;
1792        }
1793        updateThumbVertical();
1794
1795        hamburgerMenu.addEventListener('scroll', updateThumbVertical, { passive: true });
1796        window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(updateThumbVertical, 100); });
1797        new MutationObserver(updateThumbVertical).observe(hamburgerMenu, { childList: true, subtree: true, attributes: true });
1798
1799        let isDraggingVertical = false, dragStartY, thumbStartTop;
1800        thumbVertical.addEventListener('mousedown', e => {
1801            isDraggingVertical = true; dragStartY = e.clientY; thumbStartTop = parseInt(thumbVertical.style.top || '0', 10);
1802            document.body.style.userSelect = 'none';
1803        });
1804        document.addEventListener('mousemove', e => {
1805            if (!isDraggingVertical) return;
1806            const dy = e.clientY - dragStartY, newThumbTop = thumbStartTop + dy;
1807            const maxThumbTop = hamburgerMenu.clientHeight - thumbVertical.clientHeight;
1808            thumbVertical.style.top = `${Math.max(0, Math.min(newThumbTop, maxThumbTop))}px`;
1809            hamburgerMenu.scrollTop = (parseInt(thumbVertical.style.top) / maxThumbTop) * (hamburgerMenu.scrollHeight - hamburgerMenu.clientHeight);
1810        });
1811        document.addEventListener('mouseup', () => {
1812            if (isDraggingVertical) { isDraggingVertical = false; document.body.style.userSelect = ''; }
1813        });
1814    }
1815
1816    const azToggleMobile = document.querySelector('.hamburger-a-z-toggle');
1817    const azDropdownMobile = document.querySelector('.hamburger-a-z-dropdown-content');
1818    azToggleMobile.addEventListener('click', e => {
1819        e.preventDefault();
1820        const isOpen = azDropdownMobile.classList.contains('open');
1821        azDropdownMobile.classList.toggle('open');
1822        azToggleMobile.querySelector('.dropdown-arrow')?.classList.toggle('rotated', !isOpen);
1823    });
1824
1825    // **Search Functionality**
1826    const searchToggle = document.getElementById("search-mobile-toggle");
1827    const searchContainer = document.querySelector(".search-mobile-container");
1828    const searchInput = document.getElementById("search-mobile-input");
1829    const searchCloseBtn = document.getElementById("search-close-btn");
1830    const trendingContainer = document.querySelector(".trending-searches-container");
1831    const trendingList = document.querySelector(".trending-searches-list");
1832    const suggestionsContainer = document.querySelector(".suggestions-container");
1833    const searchInputDesktop = document.querySelector(".search-bar input[name='q']");
1834    const searchFormDesktop = document.querySelector(".search-bar form");
1835
1836    let dropdownDesktop;
1837    let cachedTrendingSearches = null;
1838    let cachedTrendingCelebs = null;
1839
1840    let activeCategoryDesktop = 'all';
1841    let activeCategoryMobile = 'all';
1842    let lastResultsDesktop = null;
1843    let lastResultsMobile = null;
1844
1845    const suggestionsCache = Object.create(null);
1846    let lastQueryDesktop = '';
1847    let lastQueryMobile = '';
1848
1849    function adaptiveDebounce(fn, opts = {}) {
1850        const {
1851            min = 100,
1852            max = 600,
1853            k = 1.4,
1854            alpha = 0.4,
1855            idleReset = 900,
1856            shortFloor = 260,
1857            leading = false,
1858            fastThreshold = 140,
1859            fastFloor = 360
1860        } = opts;
1861
1862        let ema = null, lastTs = 0, tId = null, lastValue = "", lastFired = "";
1863        let controller = null, idle = true;
1864
1865        const lastDeltas = [];
1866
1867        const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
1868        const now = () => (performance?.now?.() ?? Date.now());
1869
1870        function computeDelay(value, delta) {
1871            if (delta != null) {
1872                ema = ema == null ? delta : (alpha * delta + (1 - alpha) * ema);
1873                lastDeltas.push(delta);
1874                if (lastDeltas.length > 3) lastDeltas.shift();
1875            }
1876
1877            let base = ema == null ? shortFloor : k * ema;
1878
1879            if (value.length <= 2) base = Math.max(base, shortFloor);
1880
1881            const isFastBurst = lastDeltas.length >= 2 && lastDeltas.every(d => d < fastThreshold);
1882            if (isFastBurst) base = Math.max(base, fastFloor);
1883
1884            if (/[ \t.,!?;:)]$/.test(value)) base = Math.min(base, min);
1885
1886            return clamp(Math.round(base), min, max);
1887        }
1888
1889        async function fire(value) {
1890            tId = null;
1891            if (value === lastFired) return;
1892            lastFired = value;
1893
1894            if (controller) controller.abort();
1895            controller = new AbortController();
1896
1897            try {
1898                await fn(value, { signal: controller.signal });
1899            } catch (err) {}
1900        }
1901
1902        function schedule(value) {
1903            const ts = now();
1904            const delta = lastTs ? (ts - lastTs) : null;
1905            const isIdleBurst = lastTs && (ts - lastTs) >= idleReset;
1906            lastTs = ts;
1907
1908            if ((idle || isIdleBurst) && leading && value.length > 0) {
1909                idle = false;
1910                fire(value);
1911                return;
1912            }
1913
1914            idle = false;
1915            const delay = computeDelay(value, delta);
1916            if (tId) clearTimeout(tId);
1917            tId = setTimeout(() => fire(value), delay);
1918        }
1919
1920        function wrapper(value) {
1921            if (!value) {
1922                if (tId) clearTimeout(tId);
1923                tId = null;
1924                lastFired = "";
1925                if (controller) controller.abort();
1926                controller = null;
1927                idle = true;
1928                lastDeltas.length = 0;
1929                return;
1930            }
1931            if (lastTs && (now() - lastTs) >= idleReset) {
1932                idle = true;
1933                lastDeltas.length = 0;
1934            }
1935            lastValue = value;
1936            schedule(value);
1937        }
1938
1939        wrapper.flush = () => {
1940            if (tId) {
1941                clearTimeout(tId);
1942                tId = null;
1943                fire(lastValue);
1944            }
1945        };
1946
1947        wrapper.cancel = () => {
1948            if (tId) clearTimeout(tId);
1949            tId = null;
1950            if (controller) controller.abort();
1951            controller = null;
1952        };
1953
1954        return wrapper;
1955    }
1956
1957    function escapeRegExp(str) {
1958        return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1959    }
1960
1961    async function fetchWithTimeout(url, options, timeout = 1500) {
1962        const controller = new AbortController();
1963        const timeoutId = setTimeout(() => controller.abort(), timeout);
1964        try {
1965            const response = await fetch(url, { ...options, signal: controller.signal });
1966            clearTimeout(timeoutId);
1967            return response;
1968        } catch (error) {
1969            clearTimeout(timeoutId);
1970            throw error;
1971        }
1972    }
1973
1974    async function safeFetch(primaryUrl, fallbackUrl, options = {}, timeout = 1500) {
1975        try {
1976            const response = await fetchWithTimeout(primaryUrl, options, timeout);
1977            if (!response.ok) throw new Error(`Primary fetch failed: ${response.status}`);
1978            return response;
1979        } catch (error) {
1980            if (error.name === 'AbortError') {
1981                console.warn(`Primary URL timed out after ${timeout}ms`);
1982            }
1983            try {
1984                const response = await fetchWithTimeout(fallbackUrl, options, timeout);
1985                if (!response.ok) throw new Error(`Fallback fetch failed: ${response.status}`);
1986                return response;
1987            } catch (fallbackError) {
1988                if (fallbackError.name === 'AbortError') {
1989                    throw new Error(`Fallback URL timed out after ${timeout}ms`);
1990                }
1991                throw fallbackError;
1992            }
1993        }
1994    }
1995
1996    function renderCelebItem(item, q = '') {
1997        let { thumb, url, text } = item;
1998        thumb = varValues.urlPrefixCelebsMovies + thumb.replace('/portfilio/', '/circularpic/');
1999        url = varValues.domainFull + url;
2000
2001        const nameHighlighted = q
2002            ? text.replace(new RegExp(escapeRegExp(q), 'gi'), m => `<b>${m}</b>`)
2003            : text;
2004
2005        return `
2006        <div class="trending-celeb-item">
2007          <a href="${url}">
2008            <div class="trending-celeb-content">
2009              <div class="trending-celeb-thumb">
2010                <img src="${thumb}" alt="${nameHighlighted} profile image" loading="lazy">
2011              </div>
2012              <div class="trending-celeb-info">
2013                <div class="trending-celeb-name">${nameHighlighted}</div>
2014              </div>
2015            </div>
2016          </a>
2017        </div>
2018      `;
2019    }
2020
2021    function renderTrendingCelebsSection(data, q = '') {
2022        return `
2023        <div class="trending-celebs-list">
2024            ${data.map(d => renderCelebItem(d, q)).join('')}
2025        </div>
2026      `;
2027    }
2028
2029    function getCacheKey(q) {
2030        return String(q || '').trim().toLowerCase();
2031    }
2032
2033    function getCacheForQuery(q) {
2034        const key = getCacheKey(q);
2035        if (!suggestionsCache[key]) {
2036            suggestionsCache[key] = { initial: null, fullByType: {} };
2037        }
2038        return suggestionsCache[key];
2039    }
2040
2041    function getRecentSearches(limit = 8) {
2042        try {
2043            const raw = localStorage.getItem("recentSearches");
2044            if (!raw) return [];
2045            const arr = JSON.parse(raw);
2046            if (!Array.isArray(arr)) return [];
2047            const deduped = [];
2048            for (let i = 0; i < arr.length && deduped.length < limit; i++) {
2049                const term = String(arr[i]).trim();
2050                if (term && !deduped.includes(term)) deduped.push(term);
2051            }
2052            return deduped.slice(0, limit);
2053        } catch {
2054            return [];
2055        }
2056    }
2057
2058    function setRecentSearches(arr) {
2059        if (arr.length === 0) {
2060            localStorage.removeItem("recentSearches");
2061        } else {
2062            localStorage.setItem("recentSearches", JSON.stringify(arr));
2063        }
2064    }
2065
2066    function removeRecentItem(term) {
2067        const current = getRecentSearches(9999);
2068        const next = current.filter(t => t !== term);
2069        setRecentSearches(next);
2070
2071        document.querySelectorAll('li[data-term="' + term + '"]').forEach(li => li.remove());
2072
2073        const anyLeft = getRecentSearches(1).length > 0;
2074        const desktopGroup = dropdownDesktop ? dropdownDesktop.querySelector('[data-group="recent"]') : null;
2075        const mobileContainer = document.querySelector(".recent-searches-container");
2076
2077        if (!anyLeft) {
2078            if (desktopGroup) desktopGroup.remove();
2079            if (mobileContainer) mobileContainer.remove();
2080        }
2081    }
2082
2083    function clearAllRecent() {
2084        localStorage.removeItem("recentSearches");
2085
2086        const desktopGroup = dropdownDesktop ? dropdownDesktop.querySelector('[data-group="recent"]') : null;
2087        if (desktopGroup) desktopGroup.remove();
2088
2089        const mobileContainer = document.querySelector(".recent-searches-container");
2090        if (mobileContainer) mobileContainer.remove();
2091    }
2092
2093    async function fetchTrendingCelebs() {
2094        if (cachedTrendingCelebs) return cachedTrendingCelebs;
2095        try {
2096            const resp = await fetch(`https://${varValues.cdn}/js/search/trending-celebs.json`);
2097            if (!resp.ok) throw new Error("Failed to load trending celebs");
2098            const items = await resp.json();
2099            cachedTrendingCelebs = items;
2100            return cachedTrendingCelebs;
2101        } catch {
2102            cachedTrendingCelebs = [];
2103            return [];
2104        }
2105    }
2106
2107    async function fetchTrendingSearches() {
2108        if (cachedTrendingSearches) return cachedTrendingSearches;
2109        try {
2110            let countryCode = 'US';
2111            const countryCookie = getCookie("countryData");
2112            if (countryCookie) {
2113                try {
2114                    const data = JSON.parse(countryCookie);
2115                    if (data.country_code) countryCode = data.country_code;
2116                } catch (e) {
2117                    console.error('Error parsing country cookie:', e);
2118                }
2119            }
2120
2121            let sexualityParam = '';
2122            if (varValues.gender === 'fans' || varValues.gender === 'feet') {
2123                const s = localStorage.getItem('sexuality');
2124                const mapped = s === 'Straight' ? 'f' : s === 'Gay' ? 'm' : s === 'Trans' ? 't' : 'f';
2125                sexualityParam = `&sexuality=${mapped}`;
2126            }
2127
2128            const { token, sid } = await getSearchTokenFor(PRIMARY_BASE);   // minted on the origin, always
2129            const data = await withEdgeFallback((base) =>
2130                fetchTrendingFrom(base, { token, sid, gender: varValues.gender, countryCode, sexualityParam })
2131            );
2132            const items = Array.isArray(data?.data) ? data.data : [];
2133            cachedTrendingSearches = items.map(i => i.query).filter(Boolean).slice(0, 12);
2134            return cachedTrendingSearches;
2135        } catch (err) {
2136            console.error('Error fetching trending searches:', err);
2137            cachedTrendingSearches = [];
2138            return [];
2139        }
2140    }
2141
2142    searchFormDesktop.addEventListener("click", async (e) => {
2143        if (e.target.closest(".search-suggestions-dropdown")) return;
2144
2145        searchInputDesktop.focus();
2146
2147        const val = searchInputDesktop.value.trim();
2148        const key = getCacheKey(val);
2149
2150        if (val.length >= 2) {
2151            if (dropdownDesktop && dropdownDesktop.style.display !== "none" && lastResultsDesktop && lastQueryDesktop === key) return;
2152            await renderDesktopByQuery(val);
2153        } else {
2154            if (dropdownDesktop && dropdownDesktop.style.display !== "none" && !lastResultsDesktop) return;
2155            await renderExploreDesktop();
2156        }
2157    });
2158
2159    function ensureDesktopDropdown() {
2160        if (dropdownDesktop) return dropdownDesktop;
2161        dropdownDesktop = document.createElement("div");
2162        dropdownDesktop.className = "search-suggestions-dropdown";
2163        searchFormDesktop.appendChild(dropdownDesktop);
2164
2165        dropdownDesktop.addEventListener("click", function(e) {
2166            const clearBtn = e.target.closest(".clear-all-btn");
2167            const removeBtn = e.target.closest(".remove-recent-btn");
2168
2169            if (clearBtn || removeBtn) {
2170                e.preventDefault();
2171                e.stopPropagation();
2172            }
2173
2174            if (clearBtn) {
2175                clearAllRecent();
2176                return;
2177            }
2178            if (removeBtn) {
2179                const term = removeBtn.getAttribute("data-term");
2180                if (term) removeRecentItem(term);
2181            }
2182        });
2183
2184        dropdownDesktop.addEventListener("mousedown", function(e) {
2185            if (e.target.closest(".clear-all-btn") || e.target.closest(".remove-recent-btn")) {
2186                e.stopPropagation();
2187            }
2188        });
2189
2190        return dropdownDesktop;
2191    }
2192
2193    function setDesktopExploreSkeleton() {
2194        const dd = ensureDesktopDropdown();
2195        dd.innerHTML = `
2196            <div class="explore-groups">
2197            <div class="explore-group" data-group="trending-searches">
2198                <h3 class="explore-title">Trending Searches</h3>
2199                <ul class="explore-list explore-trending"></ul>
2200            </div>
2201            <div class="explore-group" data-group="trending-celebs">
2202                <div class="explore-title-row">
2203                    <h3 class="explore-title">Trending Celebs</h3>
2204                    <a href="/browse/celebs/popular/days/1.html" class="view-all-btn">View All</a>
2205                </div>
2206                <div class="vertical-list-mount"></div>
2207            </div>
2208            </div>
2209        `;
2210        return dd;
2211    }
2212
2213    function buildRecentItem(term) {
2214        return `
2215            <li class="recent-row" data-term="${term}">
2216                <a href="/search.html?q=${encodeURIComponent(term)}">${term}</a>
2217                <button type="button" class="remove-recent-btn" aria-label="Remove" data-term="${term}">×</button>
2218            </li>
2219        `;
2220    }
2221
2222    function fillListSimple(ul, items) {
2223        if (!ul) return;
2224        ul.innerHTML = items.map(s => `<li><a href="/search.html?q=${encodeURIComponent(s)}">${s}</a></li>`).join("");
2225    }
2226
2227    function fillRecentList(ul, items) {
2228        if (!ul) return;
2229        ul.innerHTML = items.map(s => buildRecentItem(s)).join("");
2230    }
2231
2232    async function renderExploreDesktop() {
2233        const dd = setDesktopExploreSkeleton();
2234
2235        const recent = getRecentSearches();
2236        const trending = await fetchTrendingSearches();
2237
2238        // Conditionally inject Recent Searches group only if there is at least one item
2239        if (recent.length > 0) {
2240            const recentGroup = document.createElement("div");
2241            recentGroup.className = "explore-group";
2242            recentGroup.setAttribute("data-group", "recent");
2243            recentGroup.innerHTML = `
2244          <div class="explore-title-row">
2245            <h3 class="explore-title">Recent Searches</h3>
2246            <button type="button" class="clear-all-btn" data-target="recent">Clear all</button>
2247          </div>
2248          <ul class="explore-list explore-recent"></ul>
2249        `;
2250            const groupsRoot = dd.querySelector(".explore-groups");
2251            const trendingBlock = dd.querySelector('[data-group="trending-searches"]');
2252            if (groupsRoot) {
2253                if (trendingBlock) {
2254                    groupsRoot.insertBefore(recentGroup, trendingBlock);
2255                } else {
2256                    groupsRoot.prepend(recentGroup);
2257                }
2258            }
2259        }
2260
2261        const recentUL = dd.querySelector(".explore-recent");
2262        const trendingUL = dd.querySelector(".explore-trending");
2263        const celebsMount = dd.querySelector(".vertical-list-mount");
2264
2265        if (trendingUL) {
2266            fillListSimple(trendingUL, trending);
2267        }
2268        if (recentUL) {
2269            fillRecentList(recentUL, recent);
2270        }
2271        if (celebsMount) {
2272            const celebs = await fetchTrendingCelebs();
2273            celebsMount.innerHTML = renderTrendingCelebsSection(celebs);
2274        }
2275
2276        dd.style.display = "block";
2277    }
2278
2279    function hideDesktopDropdown() {
2280        if (dropdownDesktop) dropdownDesktop.style.display = "none";
2281        if (typeof remoteSearchDesktop?.cancel === "function") remoteSearchDesktop.cancel();
2282    }
2283
2284    async function ensureMobileExploreContainers() {
2285        let recentC = document.querySelector(".recent-searches-container");
2286        if (!recentC) {
2287            recentC = document.createElement("div");
2288            recentC.className = "recent-searches-container";
2289            recentC.innerHTML = `
2290                <div class="mobile-title-row">
2291                    <h2 class="explore-title">Recent Searches</h2>
2292                    <button type="button" class="clear-all-btn" data-target="recent">Clear all</button>
2293                </div>
2294                <ul class="recent-searches-list"></ul>
2295            `;
2296            const anchor = document.querySelector(".trending-searches-container");
2297            if (anchor) anchor.insertAdjacentElement("beforebegin", recentC);
2298            else searchContainer.appendChild(recentC);
2299
2300            recentC.addEventListener("click", function(e) {
2301                const clearBtn = e.target.closest(".clear-all-btn");
2302                const removeBtn = e.target.closest(".remove-recent-btn");
2303
2304                if (clearBtn || removeBtn) {
2305                    e.preventDefault();
2306                    e.stopPropagation();
2307                }
2308
2309                if (clearBtn) {
2310                    clearAllRecent();
2311                    return;
2312                }
2313                if (removeBtn) {
2314                    const term = removeBtn.getAttribute("data-term");
2315                    if (term) removeRecentItem(term);
2316                }
2317            });
2318        }
2319
2320        let celebsC = document.querySelector(".trending-celebs-container");
2321        if (!celebsC) {
2322            celebsC = document.createElement("div");
2323            celebsC.className = "trending-celebs-container";
2324            celebsC.innerHTML = `
2325            <div class="explore-title-row">
2326                <h2 class="explore-title">Trending Celebs</h2>
2327                <a href="/browse/celebs/popular/days/1.html" class="view-all-btn">View All</a>
2328            </div>
2329            <div class="vertical-list-mount"></div>
2330          `;
2331            searchContainer.appendChild(celebsC);
2332        }
2333
2334        let mobileResults = document.querySelector(".suggestions-container");
2335        if (mobileResults && !mobileResults.querySelector(".results-host")) {
2336            const host = document.createElement("div");
2337            host.className = "results-host";
2338            mobileResults.appendChild(host);
2339        }
2340    }
2341
2342    async function renderExploreMobile() {
2343        await ensureMobileExploreContainers();
2344
2345        const trending = await fetchTrendingSearches();
2346        const recent = getRecentSearches();
2347
2348        if (trendingList) {
2349            trendingList.innerHTML = trending
2350                .map(s => `<li><a href="/search.html?q=${encodeURIComponent(s)}">${s}</a></li>`)
2351                .join("");
2352        }
2353
2354        const recentUL = document.querySelector(".recent-searches-list");
2355        if (recentUL) {
2356            fillRecentList(recentUL, recent);
2357        }
2358
2359        const celebsMount = document.querySelector(".trending-celebs-container .vertical-list-mount");
2360        if (celebsMount) {
2361            const celebs = await fetchTrendingCelebs();
2362            celebsMount.innerHTML = renderTrendingCelebsSection(celebs);
2363        }
2364
2365        const recentC = document.querySelector(".recent-searches-container");
2366        const celebsC = document.querySelector(".trending-celebs-container");
2367
2368        if (trendingContainer) {
2369            trendingContainer.style.display = "block";
2370        }
2371        if (recentC) {
2372            recentC.style.display = recent && recent.length > 0 ? "block" : "none";
2373        }
2374        if (celebsC) {
2375            celebsC.style.display = "block";
2376        }
2377        if (suggestionsContainer) {
2378            suggestionsContainer.style.display = "none";
2379        }
2380    }
2381
2382    function normalizeResultsPayload(payload) {
2383        const empty = { celebs: [], movies: [], stories: [], videos: [] };
2384        const data = payload?.data || empty;
2385        const isFeet = varValues.gender === 'feet';
2386
2387        const mapType = (arr, type) => (Array.isArray(arr) ? arr : []).map(item => {
2388            let { url, text, avg_rating, celeb_id } = item;
2389            // Feet celebs: /view/celeb/m/madonna.html -> /view/celeb/m/34620/madonna.html
2390            if (isFeet && type === 'celebs' && celeb_id && url && url.startsWith('/view/celeb/')) {
2391                const parts = url.split('/');
2392                parts.splice(4, 0, String(celeb_id));
2393                url = parts.join('/');
2394            }
2395            if (type === 'celebs' || type === 'movies' || type === 'stories' || type === 'videos') {
2396                url = varValues.domainFull + url;
2397            }
2398            return { url, text, avg_rating };
2399        });
2400
2401        return {
2402            celebs: mapType(data.celebs, 'celebs'),
2403            movies: mapType(data.movies, 'movies'),
2404            stories: mapType(data.stories, 'stories'),
2405            videos: mapType(data.videos, 'videos')
2406        };
2407    }
2408
2409    function buildTabs(counts, active) {
2410        return `
2411            <div class="media__picker">
2412                <button type="button" class="media__picker-button ${active === 'all' ? 'media__picker-button--active' : ''}" data-button-type="all">All</button>
2413                ${counts.videos > 0 ? `<button type="button" class="media__picker-button ${active === 'videos' ? 'media__picker-button--active' : ''}" data-button-type="videos">Videos</button>` : ''}
2414                ${counts.celebs > 0 ? `<button type="button" class="media__picker-button ${active === 'celebs' ? 'media__picker-button--active' : ''}" data-button-type="celebs">Celebs</button>` : ''}
2415                ${counts.movies > 0 ? `<button type="button" class="media__picker-button ${active === 'movies' ? 'media__picker-button--active' : ''}" data-button-type="movies">Movies</button>` : ''}
2416                ${counts.stories > 0 ? `<button type="button" class="media__picker-button ${active === 'stories' ? 'media__picker-button--active' : ''}" data-button-type="stories">Stories</button>` : ''}
2417            </div>
2418        `;
2419    }
2420
2421    function computeCounts(results) {
2422        return {
2423            celebs: results.celebs.length,
2424            movies: results.movies.length,
2425            stories: results.stories.length,
2426            videos: results.videos.length
2427        };
2428    }
2429
2430    function hasAnyResults(results) {
2431        return !!(
2432            (results?.celebs && results.celebs.length) ||
2433            (results?.movies && results.movies.length) ||
2434            (results?.stories && results.stories.length) ||
2435            (results?.videos && results.videos.length)
2436        );
2437    }
2438
2439    function renderResultsDesktop(results) {
2440        if (!hasAnyResults(results)) { hideDesktopDropdown(); return; }
2441        lastResultsDesktop = results;
2442        const dd = ensureDesktopDropdown();
2443        const counts = computeCounts(results);
2444
2445        if (activeCategoryDesktop === 'all') {
2446            dd.innerHTML = `
2447                ${buildTabs(counts, activeCategoryDesktop)}
2448                <div class="explore-groups">
2449                    ${results.celebs.length ? `
2450                        <div class="explore-group" data-type="celebs">
2451                            <h3 class="explore-title">Celebs</h3>
2452                            <ul class="explore-list">
2453                                ${results.celebs.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2454                            </ul>
2455                        </div>` : ``}
2456                    ${results.movies.length ? `
2457                        <div class="explore-group" data-type="movies">
2458                            <h3 class="explore-title">Movies</h3>
2459                            <ul class="explore-list">
2460                                ${results.movies.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2461                            </ul>
2462                        </div>` : ``}
2463                    ${results.stories.length ? `
2464                        <div class="explore-group" data-type="stories">
2465                            <h3 class="explore-title">Stories</h3>
2466                            <ul class="explore-list">
2467                                ${results.stories.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2468                            </ul>
2469                        </div>` : ``}
2470                    ${results.videos.length ? `
2471                        <div class="explore-group" data-type="videos">
2472                            <h3 class="explore-title">Videos</h3>
2473                            <ul class="explore-list">
2474                                ${results.videos.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2475                            </ul>
2476                        </div>` : ``}
2477                </div>
2478            `;
2479        } else {
2480            const set = results[activeCategoryDesktop] || [];
2481            const listHTML = set.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("") || `<li>No results</li>`;
2482
2483            dd.innerHTML = `
2484                ${buildTabs(counts, activeCategoryDesktop)}
2485                <ul class="explore-list results-list">${listHTML}</ul>
2486            `;
2487        }
2488
2489        dd.style.display = "block";
2490
2491        dd.querySelectorAll('.media__picker-button').forEach(btn => {
2492            btn.addEventListener('click', async (e) => {
2493                e.preventDefault();
2494                e.stopPropagation();
2495
2496                const next = btn.getAttribute('data-button-type');
2497                activeCategoryDesktop = next;
2498
2499                if (next === 'all') {
2500                    const bucket = getCacheForQuery(lastQueryDesktop);
2501                    const base = bucket?.initial || lastResultsDesktop || { celebs:[], movies:[], stories:[], videos:[] };
2502                    renderResultsDesktop(base);
2503                    return;
2504                }
2505
2506                const merged = await ensureTypeData(lastQueryDesktop, next);
2507                renderResultsDesktop(merged || lastResultsDesktop);
2508            });
2509        });
2510    }
2511
2512    function renderResultsMobile(results) {
2513        if (!hasAnyResults(results)) { if (suggestionsContainer) suggestionsContainer.style.display = "none"; return; }
2514        lastResultsMobile = results;
2515        const host = suggestionsContainer?.querySelector('.results-host');
2516        if (!host) return;
2517
2518        const counts = computeCounts(results);
2519
2520        if (activeCategoryMobile === 'all') {
2521            host.innerHTML = `
2522                ${buildTabs(counts, activeCategoryMobile)}
2523                <div class="explore-groups">
2524                    ${results.celebs.length ? `
2525                        <div class="explore-group" data-type="celebs">
2526                            <h3 class="explore-title">Celebs</h3>
2527                            <ul class="suggestions-list">
2528                                ${results.celebs.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2529                            </ul>
2530                        </div>` : ``}
2531                    ${results.movies.length ? `
2532                        <div class="explore-group" data-type="mov
2532ies">
2533                            <h3 class="explore-title">Movies</h3>
2534                            <ul class="suggestions-list">
2535                                ${results.movies.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2536                            </ul>
2537                        </div>` : ``}
2538                    ${results.stories.length ? `
2539                        <div class="explore-group" data-type="stories">
2540                            <h3 class="explore-title">Stories</h3>
2541                            <ul class="suggestions-list">
2542                                ${results.stories.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2543                            </ul>
2544                        </div>` : ``}
2545                    ${results.videos.length ? `
2546                        <div class="explore-group" data-type="videos">
2547                            <h3 class="explore-title">Videos</h3>
2548                            <ul class="suggestions-list">
2549                                ${results.videos.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("")}
2550                            </ul>
2551                        </div>` : ``}
2552                </div>
2553            `;
2554        } else {
2555            const set = results[activeCategoryMobile] || [];
2556            host.innerHTML = `
2557                ${buildTabs(counts, activeCategoryMobile)}
2558                <ul class="suggestions-list results-list">
2559                    ${set.map(({url, text}) => `<li><a href="${url}">${text}</a></li>`).join("") || `<li>No results</li>`}
2560                </ul>
2561            `;
2562        }
2563
2564        suggestionsContainer.style.display = "block";
2565
2566        host.querySelectorAll('.media__picker-button').forEach(btn => {
2567            btn.addEventListener('click', async (e) => {
2568                e.preventDefault();
2569                e.stopPropagation();
2570
2571                const next = btn.getAttribute('data-button-type');
2572                activeCategoryMobile = next;
2573
2574                if (next === 'all') {
2575                    const bucket = getCacheForQuery(lastQueryMobile);
2576                    const base = bucket?.initial || lastResultsMobile || { celebs:[], movies:[], stories:[], videos:[] };
2577                    renderResultsMobile(base);
2578                    return;
2579                }
2580
2581                const merged = await ensureTypeData(lastQueryMobile, next);
2582                renderResultsMobile(merged || lastResultsMobile);
2583            });
2584        });
2585
2586        const recentC = document.querySelector(".recent-searches-container");
2587        const celebsC = document.querySelector(".trending-celebs-container");
2588        if (recentC) recentC.style.display = "none";
2589        if (trendingContainer) trendingContainer.style.display = "none";
2590        if (celebsC) celebsC.style.display = "none";
2591    }
2592
2593    /* How long a failed suggestion tier is skipped for. Long enough that a
2594     * burst of typing does not re-pay the same timeout per character, short
2595     * enough that a tier coming back is picked up within a few seconds. */
2596    const SUGGEST_TIER_COOLDOWN_MS = 15000;
2597    const suggestTierCooldown = Object.create(null);
2598
2599    async function fetchSuggestionsPayload(query, opts = {}) {
2600        const { limit, type, offset, signal } = opts;
2601
2602        const resolvedSexuality = (typeof sexuality !== 'undefined' && sexuality)
2603            ? sexuality
2604            : (varValues?.sexuality || '');
2605        const sexualityParam = resolvedSexuality
2606            ? `&sexuality=${encodeURIComponent(resolvedSexuality)}`
2607            : '';
2608
2609        const typeParam   = type   ? `&type=${encodeURIComponent(type)}`   : '';
2610        const limitParam  = Number.isFinite(limit)  ? `&limit=${limit}`    : '';
2611        const offsetParam = Number.isFinite(offset) ? `&offset=${offset}`  : '';
2612
2613        const path =
2614            `/exp/search-suggestions` +
2615            `?q=${encodeURIComponent(query)}` +
2616            `&gender=${encodeURIComponent(varValues.gender)}` +
2617            `${sexualityParam}${typeParam}${limitParam}${offsetParam}`;
2618
2619        /*
2620         * THE THREE TIERS ARE THREE HOSTS, not three branches inside one.
2621         *
2622         *   NEON_BASE     mainNeon      Neon
2623         *   PRIMARY_BASE  main          Turso
2624         *   D1_BASE       the Worker    D1
2625         *
2626         * Same shape as SearchPage.jsx's SEARCH_TIERS, and for the same reason:
2627         * each host is a separate failure domain, so a dead one surfaces as a
2628         * failure this loop can walk past instead of being absorbed server-side
2629         * into a slower answer that looks healthy. functions/index.js says the
2630         * same thing from the other end.
2631         *
2632         * BUDGETS ARE TIGHTER THAN /search's 2000/1500/3000, deliberately.
2633         * A search runs once; suggestions run on every keystroke, so a tier that
2634         * is merely slow costs the user a laggy dropdown on every character
2635         * rather than one slow page. Two things bound the damage: adaptiveDebounce
2636         * aborts the in-flight request when the next character arrives, and a
2637         * tier that times out is skipped for that keystroke only, not remembered.
2638         *
2639         * NEON GETS THE MOST OF THE THREE because it is the one host the page has
2640         * not already talked to. PRIMARY_BASE is warm by the time anyone types -
2641         * page load has already called /search-token and /most-searched-queries
2642         * against it - so it pays neither the TLS handshake nor the CORS preflight
2643         * that x-st/x-sid force on a first request. mainNeon pays both on the
2644         * first keystroke of a page view. Measured for /search on 2026-09-05:
2645         * handshake 326-442ms, preflight 233ms, both INSIDE this budget.
2646         *
2647         * REVISIT IF preconnect LANDS. A <link rel="preconnect"> for the mainNeon
2648         * host would move the handshake into page load and let neon come down to
2649         * ~700ms. That is the better fix; 1200 is sized for its absence.
2650         *
2651         * A tier whose base is missing is SKIPPED, not attempted, so this file
2652         * keeps working if NEON_BASE is ever removed.
2653         *
2654         * ON AN EDGE HOST THE FIRST TIER IS THE EDGE, and it replaces neon rather
2655         * than joining the walk in front of it; see suggestFirstTier.
2656         */
2657        const SUGGEST_TIERS = [
2658            suggestFirstTier(),
2659            { name: 'turso', base: typeof PRIMARY_BASE !== 'undefined' ? PRIMARY_BASE : null, timeout: 1200 },
2660            { name: 'd1',    base: typeof D1_BASE      !== 'undefined' ? D1_BASE      : null, timeout: 2000 },
2661        ];
2662
2663        /*
2664         * One signal per attempt, firing on either the caller's abort or this
2665         * tier's timeout, so a keystroke cancels in flight and a slow host still
2666         * gets cut off. `timedOut` tells them apart afterwards: a caller abort
2667         * ends the walk, a timeout moves to the next tier.
2668         */
2669        function tierSignal(ms) {
2670            const ctl = new AbortController();
2671            const state = { timedOut: false };
2672            const onAbort = () => ctl.abort();
2673
2674            if (signal) {
2675                if (signal.aborted) ctl.abort();
2676                else signal.addEventListener('abort', onAbort, { once: true });
2677            }
2678            const timer = setTimeout(() => { state.timedOut = true; ctl.abort(); }, ms);
2679
2680            state.signal = ctl.signal;
2681            state.done = () => {
2682                clearTimeout(timer);
2683                if (signal) signal.removeEventListener('abort', onAbort);
2684            };
2685            return state;
2686        }
2687
2688        async function fetchFromTier(base, timeout, tokenHost) {
2689            /*
2690             * THE TOKEN IS FETCHED BEFORE THE TIMER STARTS, and that ordering is
2691             * the fix for a bug, not a stylistic choice. With the timer started
2692             * first, a cold mint - bounded separately at TOKEN_MINT_TIMEOUT_MS,
2693             * 1000ms - ate almost the whole tier budget, so the first keystroke
2694             * of a page view would time out on Neon and fall to Turso every time.
2695             * That reads as 'Neon is slow' and is really 'Neon was never reached'.
2696             * The mint carries its own timeout, so nothing here is unbounded.
2697             */
2698            const { token, sid } = await getSearchTokenFor(tokenHost);
2699
2700            const tier = tierSignal(timeout);
2701            try {
2702                const json = await fetchSuggestionsFrom(base, path, { token, sid, signal: tier.signal });
2703                return normalizeResultsPayload(json);
2704            } finally {
2705                tier.done();
2706            }
2707        }
2708
2709        async function tryFetch(base, timeout, tokenBase = base) {
2710            const tokenHost = tokenBase.replace(/\/$/, '');
2711            try {
2712                return await fetchFromTier(base, timeout, tokenHost);
2713            } catch (err) {
2714                /*
2715                 * AZ-269: on AUTH_401, purge the token and retry once. TokenManager trusts the
2716                 * browser's clock, so a slow one replays an expired token on every tier until
2717                 * reload. A refusal that isn't expiry survives the re-mint and fails as before.
2718                 */
2719                if (!String(err?.message).startsWith('AUTH_401') || signal?.aborted) throw err;
2720                invalidateSearchTokenFor(tokenHost);
2721                return fetchFromTier(base, timeout, tokenHost);
2722            }
2723        }
2724
2725        const failures = [];
2726        for (const { name, base, timeout, tokenBase } of SUGGEST_TIERS) {
2727            if (!base) continue;
2728            if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
2729
2730            /*
2731             * A tier that just failed is skipped rather than retried. Without
2732             * this a stalled mainNeon costs its full budget on EVERY keystroke -
2733             * the walk has no memory, so each character pays the same timeout
2734             * again before reaching a host that works. One failure parks the tier
2735             * for SUGGEST_TIER_COOLDOWN_MS and the dropdown stays responsive
2736             * through an outage instead of degrading with every letter typed.
2737             */
2738            if (suggestTierCooldown[name] > Date.now()) {
2739                failures.push(name + ': skipped, cooling down');
2740                continue;
2741            }
2742
2743            try {
2744                return await tryFetch(base, timeout, tokenBase);
2745            } catch (err) {
2746                /* The user typed. Stop the whole walk - the answer is already
2747                 * stale and the next keystroke has its own request in flight.
2748                 * Not a tier fault, so it must not trip the cooldown. */
2749                if (signal?.aborted) throw err;
2750                /* An edge failure takes the edge off for the page view, as it
2751                 * does for the trending strip, so after the cooldown the first
2752                 * tier is plain neon rather than the proxy again. A rejected
2753                 * token is the exception: it is not the proxy's fault, and it
2754                 * would be rejected the same way on mainNeon directly. */
2755                if (name === 'edge' && !String(err?.message).startsWith('AUTH_401')) {
2756                    disableEdgeRead(err);
2757                }
2758                suggestTierCooldown[name] = Date.now() + SUGGEST_TIER_COOLDOWN_MS;
2759                failures.push(name + ': ' + (err?.message || err));
2760            }
2761        }
2762
2763        /*
2764         * Every host is gone, or every one is cooling down. This was an empty
2765         * catch returning undefined, which hasAnyResults() read as 'no matches'
2766         * and the dropdown simply hid - so a total outage was indistinguishable
2767         * from a query with no content, in the UI and in the telemetry both.
2768         *
2769         * The payload is MARKED, and that matters beyond logging: ensureTypeData
2770         * caches whatever comes back as the complete list for that type, so an
2771         * unmarked empty answer would pin the initial five rows as the full set
2772         * and 'more' would never load again for that query, for the rest of the
2773         * page view. A transient outage would become a permanent one.
2774         */
2775        console.error('search-suggestions failed on every tier:', failures.join(' | '));
2776        const dead = normalizeResultsPayload({ data: [] });
2777        dead.__failed = true;
2778        return dead;
2779    }
2780
2781    async function handleDesktopFormClick(e) {
2782        if (e.target.closest(".search-suggestions-dropdown")) return;
2783
2784        searchInputDesktop.focus();
2785
2786        const val = searchInputDesktop.value.trim();
2787        const key = getCacheKey(val);
2788        const bucket = getCacheForQuery(key);
2789
2790        if (val.length >= 2) {
2791            if (bucket.initial) {
2792                lastQueryDesktop = key;
2793                activeCategoryDesktop = 'all';
2794                lastResultsDesktop = bucket.initial;
2795                renderResultsDesktop(bucket.initial);
2796                return;
2797            }
2798            await renderDesktopByQuery(val);
2799        } else {
2800            if (dropdownDesktop && dropdownDesktop.style.display !== "none" && !lastResultsDesktop) return;
2801            await renderExploreDesktop();
2802        }
2803    }
2804
2805    searchFormDesktop.addEventListener("click", handleDesktopFormClick);
2806
2807    async function renderDesktopByQuery(query, opts = {}) {
2808        const { force = false } = opts;
2809        try {
2810            const key = getCacheKey(query);
2811            lastQueryDesktop = key;
2812
2813            const bucket = getCacheForQuery(key);
2814
2815            if (!force && bucket.initial) {
2816                activeCategoryDesktop = 'all';
2817                lastResultsDesktop = bucket.initial;
2818                renderResultsDesktop(bucket.initial);
2819                return;
2820            }
2821
2822            const results = await fetchSuggestionsPayload(query, { limit: 5 });
2823            if (!hasAnyResults(results)) {
2824                hideDesktopDropdown();
2825                lastResultsDesktop = null;
2826                return;
2827            }
2828
2829            bucket.initial = results;
2830
2831            activeCategoryDesktop = 'all';
2832            lastResultsDesktop = results;
2833            renderResultsDesktop(results);
2834        } catch (e) {
2835            console.error(e);
2836            hideDesktopDropdown();
2837        }
2838    }
2839
2840    async function ensureTypeData(queryKey, type) {
2841        const bucket = getCacheForQuery(queryKey);
2842        if (!bucket.initial) return null;
2843
2844        if (bucket.fullByType[type] && bucket.fullByType[type].length > 0) {
2845            return {
2846                celebs : bucket.fullByType.celebs || bucket.initial.celebs,
2847                movies : bucket.fullByType.movies || bucket.initial.movies,
2848                stories: bucket.fullByType.stories || bucket.initial.stories,
2849                videos : bucket.fullByType.videos || bucket.initial.videos
2850            };
2851        }
2852
2853        const existing = (bucket.initial[type] || []);
2854        const offset = existing.length;
2855        try {
2856            const more = await fetchSuggestionsPayload(queryKey, { type, offset });
2857
2858            /*
2859             * A total tier failure must not be cached as the complete list.
2860             * fetchSuggestionsPayload marks that case; without the check, an
2861             * outage lasting one request would pin the initial rows as the full
2862             * set for this type, and 'more' would never load again for this
2863             * query for the rest of the page view - a transient failure turned
2864             * permanent. Returning bucket.initial leaves fullByType unset, so the
2865             * next attempt retries.
2866             */
2867            if (more.__failed) return bucket.initial;
2868
2869            const appended = (more[type] && more[type].length) ? existing.concat(more[type]) : existing.slice();
2870
2871            bucket.fullByType[type] = appended;
2872
2873            return {
2874                celebs : type === 'celebs'  ? bucket.fullByType.celebs  : bucket.initial.celebs,
2875                movies : type === 'movies'  ? bucket.fullByType.movies  : bucket.initial.movies,
2876                stories: type === 'stories' ? bucket.fullByType.stories : bucket.initial.stories,
2877                videos : type === 'videos'  ? bucket.fullByType.videos  : bucket.initial.videos
2878            };
2879        } catch (err) {
2880            console.error('ensureTypeData error:', err);
2881            return bucket.initial;
2882        }
2883    }
2884
2885    const remoteSearchDesktop = adaptiveDebounce(async (q, { signal }) => {
2886        const key = getCacheKey(q);
2887        lastQueryDesktop = key;
2888
2889        const results = await fetchSuggestionsPayload(q, { limit: 5, signal });
2890        if (!hasAnyResults(results)) {
2891            hideDesktopDropdown();
2892            lastResultsDesktop = null;
2893            return;
2894        }
2895
2896        const bucket = getCacheForQuery(key);
2897        bucket.initial = results;
2898
2899        activeCategoryDesktop = 'all';
2900        lastResultsDesktop = results;
2901        renderResultsDesktop(results);
2902    }, {
2903        leading: false,
2904        min: 100,
2905        max: 600,
2906        k: 1.4,
2907        shortFloor: 260,
2908        fastThreshold: 140,
2909        fastFloor: 360,
2910        idleReset: 1000,
2911        alpha: 0.4
2912    });
2913
2914    const remoteSearchMobile = adaptiveDebounce(async (q, { signal }) => {
2915        await ensureMobileExploreContainers();
2916
2917        const key = getCacheKey(q);
2918        lastQueryMobile = key;
2919
2920        const results = await fetchSuggestionsPayload(q, { limit: 5, signal });
2921        if (!hasAnyResults(results)) {
2922            if (suggestionsContainer) suggestionsContainer.style.display = "none";
2923            lastResultsMobile = null;
2924            await renderExploreMobile();
2925            return;
2926        }
2927
2928        const bucket = getCacheForQuery(key);
2929        bucket.initial = results;
2930
2931        activeCategoryMobile = 'all';
2932        lastResultsMobile = results;
2933        renderResultsMobile(results);
2934    }, {
2935        leading: false,
2936        min: 100,
2937        max: 600,
2938        k: 1.4,
2939        shortFloor: 260,
2940        fastThreshold: 140,
2941        fastFloor: 360,
2942        idleReset: 1000,
2943        alpha: 0.4
2944    });
2945
2946    searchInputDesktop.addEventListener("input", async function() {
2947        const query = this.value.trim();
2948        if (query.length < 2) {
2949            remoteSearchDesktop.cancel();
2950            await renderExploreDesktop();
2951            return;
2952        }
2953        remoteSearchDesktop(query);
2954    });
2955
2956    searchInput.addEventListener("input", async function() {
2957        const query = this.value.trim();
2958        if (query.length < 2) {
2959            remoteSearchMobile.cancel();
2960            await renderExploreMobile();
2961            return;
2962        }
2963        remoteSearchMobile(query);
2964    });
2965
2966    searchInputDesktop.addEventListener("keydown", e => { if (e.key === "Enter") remoteSearchDesktop.flush(); });
2967    searchInput.addEventListener("keydown", e => { if (e.key === "Enter") remoteSearchMobile.flush(); });
2968    searchInputDesktop.addEventListener("blur", () => remoteSearchDesktop.flush());
2969    searchInput.addEventListener("blur", () => remoteSearchMobile.flush());
2970
2971    document.addEventListener("click", e => {
2972        const inDropdown  = e.target.closest(".search-suggestions-dropdown");
2973        const inSearchBar = e.target.closest(".search-bar");
2974        if (dropdownDesktop && !inDropdown && !inSearchBar) {
2975            hideDesktopDropdown();
2976        }
2977    });
2978
2979    searchToggle.addEventListener("click", async e => {
2980        e.preventDefault();
2981        if (window.innerWidth <= 480) {
2982            const isActive = searchContainer.classList.contains("active");
2983            searchContainer.classList.toggle("active", !isActive);
2984            document.body.style.overflow = isActive ? "auto" : "hidden";
2985            if (!isActive) {
2986                // searchInput.focus();
2987                await renderExploreMobile();
2988            }
2989        }
2990    });
2991
2992    searchCloseBtn.addEventListener("click", () => {
2993        remoteSearchMobile.cancel();
2994        searchContainer.classList.remove("active");
2995        if (suggestionsContainer) suggestionsContainer.style.display = "none";
2996        document.body.style.overflow = "auto";
2997    });
2998
2999    window.addEventListener("resize", () => {
3000        if (window.innerWidth > 480 && searchContainer.classList.contains("active")) {
3001            searchContainer.classList.remove("active");
3002            document.body.style.overflow = "auto";
3003        }
3004    });
3005
3006    // region Sexuality Dropdown
3007
3008    setupSexualityDropdown(document.querySelector('[data-sexuality-dropdown]'))
3009
3010    function setupSexualityDropdown(sexualityDropdown) {
3011        if (!sexualityDropdown) return;
3012
3013        // contains the selected option text and svg
3014        const sexualityToggle = sexualityDropdown.querySelector('.sexuality-dropdown__toggle');
3015        const mobileSexualitySelector = document.querySelector('.mobile-sexuality-selector');
3016
3017        // the dropdown menu options, each of the form <a href> <svg></svg> <span>option text</span> </a>
3018        const sexualityMenuItems = sexualityDropdown.querySelectorAll('.sexuality-dropdown__menu .mobile-sexuality-category-dropdown-item');
3019        const mobileDropdownItems = document.querySelectorAll('.mobile-sexuality-category-content .mobile-sexuality-category-dropdown-item[href]');
3020
3021        // normalize path function
3022        const normalizePath = path => path !== '/' ? path.replace(/\/+$/, '').split('?')[0] : path;
3023
3024        function updateSexualitySelection(category) {
3025            const desktopItem = [...sexualityMenuItems].find(item=>item.dataset.category === category);
3026            const mobileItem = [...mobileDropdownItems].find(item=>item.dataset.category === category);
3027            if (!desktopItem || !mobileItem) return;
3028
3029            const selectedItems = [desktopItem, mobileItem];
3030            [sexualityToggle, mobileSexualitySelector].forEach((item, index) => {
3031                const svg = selectedItems[index].querySelector('svg');
3032                if (svg) item.querySelector('svg').outerHTML = svg.outerHTML
3033                let spans = item.querySelectorAll('span');
3034                spans[spans.length - 1].textContent = category;
3035            });
3036
3037            [...sexualityMenuItems, ...mobileDropdownItems].forEac
3037h(item => item.classList.toggle('active', item.dataset.category === category));
3038            localStorage.setItem('sexuality', category);
3039        }
3040
3041        function getRedirectPath(cat, currentPath) {
3042            const basePath  = normalizePath(currentPath);
3043            // currently on / -> return expected / url
3044            if (basePath in pathToCategory || basePath === '/') return categoryToPath[cat];
3045            else if (/\/browse(?:\/|$)/.test(basePath)) {
3046                const p = basePath.split('/');
3047                const tail = [...p.slice('/' + p[1] in pathToCategory ? 2 : 1, p.length - 1)];
3048                tail.splice(0, 0, categoryToPath[cat] === '/' ? '' : categoryToPath[cat]);
3049                const currentCat = '/' + p[1] in pathToCategory ? pathToCategory['/' + p[1]] : pathToCategory['/']
3050                currentCat === cat ? tail.push(p[p.length - 1]) : tail.push('1.html');
3051                return tail.join('/') || '/';
3052            }
3053            return basePath;
3054        }
3055
3056
3057        const currentPath = window.location.pathname;
3058        const storedCategory = localStorage.getItem('sexuality');
3059        const pathToCategory = {};
3060        const categoryToPath = {};
3061        const valid = [];
3062        sexualityMenuItems.forEach(item => {
3063            const href = item.getAttribute('href');
3064            const cat  = item.dataset.category;
3065            pathToCategory[href] = cat;
3066            categoryToPath[cat] = href;
3067            valid.push(cat);
3068        });
3069
3070        const selectedCategory = valid.includes(storedCategory) ? storedCategory : valid[0]
3071        const expectedPath = getRedirectPath(storedCategory, window.location.pathname);
3072
3073        sexualityDropdown.classList.add('loading');
3074        valid.includes(storedCategory) && normalizePath(currentPath) !== normalizePath(expectedPath)
3075            ? window.location.href = expectedPath : null;
3076
3077        updateSexualitySelection(selectedCategory);
3078        sexualityDropdown.classList.remove('loading');
3079
3080        sexualityToggle.addEventListener('click', () => {
3081            sexualityDropdown.classList.toggle('open');
3082        });
3083
3084        [...sexualityMenuItems, ...mobileDropdownItems].forEach(item => {
3085            item.addEventListener('click', e => {
3086                e.preventDefault();
3087                const cat = item.dataset.category;
3088                updateSexualitySelection(cat);
3089                if (sexualityDropdown.classList.contains('open')) sexualityDropdown.classList.remove('open');
3090                const path = window.location.pathname;
3091                path === '/search.html' ? window.location.reload() : window.location.href = getRedirectPath(cat, path);
3092            });
3093        });
3094
3095        document.addEventListener('click', e => {
3096            if (sexualityDropdown.classList.contains('open') && !sexualityDropdown.contains(e.target)) {
3097                sexualityDropdown.classList.remove('open');
3098            }
3099        });
3100
3101        mobileSexualitySelector.addEventListener('click', e => {
3102            e.preventDefault();
3103            const mobileSexualityDropdown = document.querySelector('.mobile-sexuality-category-dropdown-content');
3104            const isOpen = mobileSexualityDropdown.classList.contains('open');
3105            mobileSexualityDropdown.classList.toggle('open');
3106            mobileSexualitySelector.querySelector('.dropdown-arrow')?.classList.toggle('rotated', !isOpen);
3107        });
3108
3109    }// endregion
3110
3111
3112    // **Websites Dropdown**
3113    const azWebsitesToggle = document.querySelector('.hamburger-a-z-websites-toggle');
3114    const azWebsitesDropdown = document.querySelector('.hamburger-a-z-websites-dropdown-content');
3115
3116    if (azWebsitesToggle && azWebsitesDropdown) {
3117        azWebsitesToggle.addEventListener('click', e => {
3118            e.preventDefault();
3119            const isOpen = azWebsitesDropdown.classList.contains('open');
3120            azWebsitesDropdown.classList.toggle('open');
3121            azWebsitesToggle.querySelector('.dropdown-arrow')?.classList.toggle('rotated', !isOpen);
3122        });
3123    }
3124
3125    // **Browse Dropdown**
3126    const browseToggle = document.querySelector('.browse-toggle');
3127    const browseDropdown = document.querySelector('.browse-dropdown-content');
3128
3129    browseToggle.addEventListener('click', e => {
3130        e.preventDefault();
3131        const isOpen = browseDropdown.classList.contains('open');
3132        browseDropdown.classList.toggle('open');
3133        browseToggle.querySelector('.dropdown-arrow')?.classList.toggle('rotated', !isOpen);
3134    });
3135
3136    // **Direct Link Handling**
3137    function shuffleArray(arr) {
3138        for (let i = arr.length - 1; i > 0; i--) {
3139            const j = Math.floor(Math.random() * (i + 1));
3140            [arr[i], arr[j]] = [arr[j], arr[i]];
3141        }
3142        return arr;
3143    }
3144
3145    fetch(`https://${varValues.cdn}/js/dating.json`)
3146        .then(response => {
3147            if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
3148            return response.json();
3149        })
3150        .then(data => {
3151            const {baseUrl, ads} = data;
3152
3153            const commonId = ads[0].id;
3154            const items = shuffleArray(ads[0].items);
3155
3156            const aiId = ads[1].id;
3157            const aiItems = shuffleArray(ads[1].items);
3158
3159            const { text, sub } = items[0];
3160            const {text: aiText } = aiItems[0];
3161
3162            ['direct-link-desktop', 'direct-link-navbar', 'direct-link-mobile', 'direct-link-footer', 'direct-link-video', 'direct-link-mobile-2'].forEach(id => {
3163                const el = document.getElementById(id);
3164                if (el) {
3165                    if (id === "direct-link-mobile") {
3166                        el.innerHTML = `<img src="https://cdn.aznude.com/images/svgs/water.svg" width="26" height="26" alt=""><span>${aiText}</span>`;
3167                    } else if (id === "direct-link-mobile-2"){
3168                        el.innerHTML = `<img src="https://cdn.aznude.com/images/svgs/water.svg" width="26" height="26" alt=""><span>${text}</span>`;
3169                    } else if (id === "direct-link-navbar"){
3170                        const span = el.querySelector('span');
3171                        if (span) span.textContent = aiText; else el.textContent = aiText;
3172                    } else {
3173                        const span = el.querySelector('span');
3174                        if (span) span.textContent = text; else el.textContent = text;
3175                    }
3176
3177                    if(id === "direct-link-navbar" || id === "direct-link-mobile"){
3178                        el.setAttribute('href', `${baseUrl}?z=${aiId}`);
3179                    } else {
3180                        el.setAttribute('href', `${baseUrl}?z=${commonId}&sub=${sub}`);
3181                        el.setAttribute('sub', sub);
3182                    }
3183                }
3184            });
3185        })
3186        .catch(error => {
3187
3188        });
3189
3190    // **Country Detection - Global**
3191    async function detectAndStoreCountry() {
3192        const countryCookie = getCookie("countryData");
3193        if (countryCookie) {
3194            try {
3195                return JSON.parse(countryCookie);
3196            } catch (e) {
3197                console.error('Error parsing country cookie:', e);
3198            }
3199        }
3200
3201        try {
3202            const response = await fetch('https://www.cloudflare.com/cdn-cgi/trace');
3203            const text = await response.text();
3204            const data = {};
3205            text.split('\n').forEach(line => {
3206                const [key, value] = line.split('=');
3207                if (key && value) data[key] = value;
3208            });
3209            const countryCode = data.loc;
3210            const countriesResp = await fetch(`https://${varValues.cdn}/js/countries.json`);
3211            const countries = await countriesResp.json();
3212            const country = countries.find(c => c.code === countryCode);
3213            const countryName = country ? country.name : "Unknown Country";
3214            const countryData = { country_name: countryName, country_code: countryCode };
3215            setCookie("countryData", JSON.stringify(countryData), 1);
3216            return countryData;
3217        } catch (error) {
3218            console.error('Error detecting country:', error);
3219            return null;
3220        }
3221    }
3222
3223    detectAndStoreCountry();
3224
3225    if(document.querySelector('.location-bar')){
3226        detectAndStoreCountry().then(countryData => {
3227            if (countryData && countryData.country_name && countryData.country_code) {
3228                updateCountryInfo(countryData.country_name, countryData.country_code);
3229            } else {
3230                document.querySelector('.location-bar').style.display = "none";
3231            }
3232        }).catch(error => {
3233            console.error('Error in location bar country detection:', error);
3234            document.querySelector('.location-bar').style.display = "none";
3235        });
3236
3237        function updateCountryInfo(country, countryCode) {
3238            const countryText = document.getElementById('country-text');
3239            const locationIconContainer = countryText.querySelector('.location-icon-container');
3240            countryText.innerHTML = '';
3241            countryText.appendChild(locationIconContainer);
3242            countryText.insertAdjacentHTML('beforeend', `<span class="country-text">${varValues.celeb_names} from ${country} </span>`);
3243            countryText.href = `/browse/${varValues.celeb_names.toLowerCase()}/country/${countryCode.toLowerCase()}/1.html`;
3244            countryText.insertAdjacentHTML('beforeend', `<img src="https://cdn.aznude.com/images/svgs/flags/${countryCode.toLowerCase()}.svg" alt="${country} flag" style="margin-left: 5px; vertical-align: middle; height: 13px;">`);
3245        }
3246    }
3247
3248    function updateCountryInfo(country, countryCode) {
3249        const countryText = document.getElementById('country-text');
3250        const locationIconContainer = countryText.querySelector('.location-icon-container');
3251        countryText.innerHTML = '';
3252        countryText.appendChild(locationIconContainer);
3253        countryText.insertAdjacentHTML('beforeend', `<span class="country-text">${varValues.celeb_names} from ${country} </span>`);
3254        countryText.href = `/browse/${varValues.celeb_names.toLowerCase()}/country/${countryCode.toLowerCase()}/1.html`;
3255        countryText.insertAdjacentHTML('beforeend', `<img src="https://cdn.aznude.com/images/svgs/flags/${countryCode.toLowerCase()}.svg" alt="${country} flag" style="margin-left: 5px; vertical-align: middle; height: 13px;">`);
3256    }
3257});
3258
3259window.addEventListener('load', async () => {
3260    if (getCookie('cookieConsent') === 'true') return;
3261
3262    try {
3263        const traceResponse = await fetch('https://www.cloudflare.com/cdn-cgi/trace');
3264        const traceText = await traceResponse.text();
3265        const data = {};
3266        traceText.split('\n').forEach(line => {
3267            const [key, value] = line.split('=');
3268            if (key && value) data[key] = value;
3269        });
3270        const countryCode = data.loc;
3271        const euResponse = await fetch(`https://${varValues.cdn}/js/eu.json`);
3272        const euCountries = await euResponse.json();
3273
3274        if (euCountries.includes(countryCode)) {
3275            const bar = document.createElement('div');
3276            bar.className = 'cookie-bar';
3277            bar.innerHTML = `
3278          <div class="cookie-content">
3279            <p>${varValues.title} uses cookies. <a href="/privacy.html" rel="noopener" target="_blank" tabindex="1">Learn more</a></p>
3280            <button class="got-it-btn">Got it!</button>
3281          </div>
3282        `;
3283            document.body.appendChild(bar);
3284
3285            setTimeout(() => {
3286                bar.classList.add('visible');
3287            }, 10);
3288
3289            const gotItBtn = bar.querySelector('.got-it-btn');
3290            gotItBtn.addEventListener('click', () => {
3291                bar.classList.remove('visible');
3292                setCookie('cookieConsent', 'true', 365);
3293                setTimeout(() => {
3294                    bar.remove();
3295                }, 500);
3296            });
3297        }
3298    } catch (error) {
3299        console.error('Error checking cookie consent:', error);
3300    }
3301});
3302
3303const getKeywords = function() {
3304    let keywords_data = $("#data-keywords").attr('data');
3305    let content = $("[name=keywords]").attr('content');
3306    let title = $('title').text();
3307    let description = $('[name=description]').attr('content');
3308    let combined_tags = [];
3309    if (keywords_data) combined_tags.push(keywords_data);
3310    else {
3311        if (content) combined_tags.push(content);
3312        if (title) combined_tags.push(title);
3313        if (description) combined_tags.push(description);
3314    }
3315    return combined_tags.join(', ');
3316}
3317
3318// What is this?
3319const pushTags = function() {
3320    let combined_tags = getKeywords();
3321    if (combined_tags.length > 0 && combined_tags !== '') {
3322        let i, dataElements;
3323        dataElements = document.getElementsByClassName("adsbyexoclick");
3324        for (i = 0; i<dataElements.length; i++) {
3325            dataElements[i].setAttribute("data-keywords", combined_tags);
3326        }
3327        dataElements = document.getElementsByClassName("eas6a97888e");
3328        for (i = 0; i<dataElements.length; i++) {
3329            dataElements[i].setAttribute("data-keywords", combined_tags);
3330        }
3331        dataElements = document.getElementsByClassName("eas6a97888e37");
3332        for (i = 0; i<dataElements.length; i++) {
3333            dataElements[i].setAttribute("data-keywords", combined_tags);
3334        }
3335        dataElements = document.querySelectorAll('[data-zoneid]');
3336        for (i = 0; i<dataElements.length; i++) {
3337            const value = dataElements[i].getAttribute('data-keywords');
3338            if (value !== null && value.trim() !== '') {continue;}
3339            dataElements[i].setAttribute("data-keywords", combined_tags);
3340        }
3341    }
3342};
3343pushTags();
3344
3345
3346const pushStreamTags = function() {
3347    let combined_tags = getKeywords();
3348    if (combined_tags.length > 0 && combined_tags !== '') {
3349        let i, dataElements;
3350        dataElements = document.getElementsByClassName("eas6a97888e37");
3351        for (i = 0; i<dataElements.length; i++) {
3352            dataElements[i].setAttribute("data-keywords", combined_tags);
3353        }
3354    }
3355};
3356
3357(function () {
3358    let combine_tags = getKeywords();
3359    let element = $('.outszone_1');
3360    if (element.length > 0 && window.innerWidth <= 480) {
3361        element.css('display', 'none');
3362        let agegoAttrs = '';
3363        if (document.body.classList.contains('agego-unverified')) agegoAttrs = ' data-ex_av="2" data-block-ad-types="101"';
3364        else if (document.body.classList.contains('agego-verified') || document.body.classList.contains('agego-legacy')) agegoAttrs = ' data-ex_av="1"';
3365        element.replaceWith(`<div class="out_video container"><ins class="eas6a97888e37" data-zoneid="${varValues.adIds.outstream}" data-keywords="${combine_tags}"${agegoAttrs}></ins></div>`);
3366        pushStreamTags();
3367    } else {
3368        element.find('ins').attr('class', 'eas6a97888e20');
3369        element.find('ins').attr('data-keywords', combine_tags);
3370    }
3371    (AdProvider = window.AdProvider || []).push({"serve": {}});
3372})();
3373
3374
3375// NEW HOME SCRIPTS
3376// NEW TAGS ROW SCRIPT
3377const tags_CONTAINER = document.getElementById('tagsContainer');
3378const tags_WRAPPER = document.querySelector('.tags-row-wrapper');
3379const tags_SECTION = document.querySelector('.tags-row-section');
3380const left_ARROW = document.getElementById('leftArrow');
3381const right_ARROW = document.getElementById('rightArrow');
3382let scroll_POSITION = 0;
3383const scroll_AMOUNT = 200;
3384
3385if(tags_CONTAINER){
3386    function update_ARROWS() {
3387        const max_SCROLL = tags_CONTAINER.scrollWidth - tags_WRAPPER.clientWidth;
3388
3389        left_ARROW.disabled = scroll_POSITION <= 0;
3390        right_ARROW.disabled = scroll_POSITION >= max_SCROLL;
3391
3392        if (scroll_POSITION <= 0) {
3393            tags_SECTION.classList.add('hide-left');
3394        } else {
3395            tags_SECTION.classList.remove('hide-left');
3396        }
3397
3398        if (scroll_POSITION >= max_SCROLL) {
3399            tags_SECTION.classList.add('hide-right');
3400        } else {
3401            tags_SECTION.classList.remove('hide-right');
3402        }
3403    }
3404
3405    left_ARROW.addEventListener('click', () => {
3406        scroll_POSITION -= scroll_AMOUNT;
3407        if (scroll_POSITION < 0) scroll_POSITION = 0;
3408        tags_CONTAINER.style.transform = `translateX(-${scroll_POSITION}px)`;
3409        update_ARROWS();
3410    });
3411
3412    right_ARROW.addEventListener('click', () => {
3413        const max_SCROLL = tags_CONTAINER.scrollWidth - tags_WRAPPER.clientWidth;
3414        scroll_POSITION += scroll_AMOUNT;
3415        if (scroll_POSITION > max_SCROLL) scroll_POSITION = max_SCROLL;
3416        tags_CONTAINER.style.transform = `translateX(-${scroll_POSITION}px)`;
3417        update_ARROWS();
3418    });
3419
3420    window.addEventListener('resize', () => {
3421        scroll_POSITION = 0;
3422        tags_CONTAINER.style.transform = `translateX(0px)`;
3423        update_ARROWS();
3424    });
3425
3426    window.addEventListener('load', update_ARROWS);
3427}
3428
3429// NEW VIDEOS SCRIPTS
3430function getPreviewVideo(video_html_link, sparksThumbUrl) {
3431    const link_break = video_html_link.split('/');
3432    const base_url_map = {
3433        brazzers: 'https://cdn3.aznude.com/data/preview/',
3434        realitykings: 'https://cdn4.aznude.com/preview/rk_',
3435        men: 'https://cdn5.aznude.com/preview/men_',
3436        default: sparksThumbUrl || varValues.sparksThumbUrl
3437    };
3438    let final_link;
3439    if (video_html_link.includes('/data/brazzers/videos')) {
3440        final_link = base_url_map.brazzers +
3441            link_break[link_break.length - 2] + '/' +
3442            link_break[link_break.length - 1].replace('.html', '_sum_mute.mp4');
3443    } else if (video_html_link.includes('/data/realitykings/videos')) {
3444        final_link = base_url_map.realitykings +
3445            link_break[link_break.length - 2] + '/' +
3446            link_break[link_break.length - 1].replace('.html', '_sum_mute.mp4');
3447    } else if (video_html_link.includes('/data/men/videos')) {
3448        final_link = base_url_map.men +
3449            link_break[link_break.length - 2] + '/' +
3450            link_break[link_break.length - 1].replace('.html', '_sum_mute.mp4');
3451    } else {
3452        // The favourites page lists other sites' items too, so the base comes
3453        // from the item's own host; the page's sparksThumbUrl is only the
3454        // fallback for relative links and unknown hosts. Matched on the host or
3455        // a subdomain of it, never a substring: 'aznude.com' must not match
3456        // aznudefeet.com or aznude.com.evil.example.
3457        const sparksBaseByHost = {
3458            'aznude.com': 'https://cdn2.aznude.com/sparkthumbs/',
3459            'azmen.com': 'https://cdn-men.aznude.com/sparkthumbs/',
3460            'azfans.com': 'https://cdn2.azfans.com/sparkthumbs/',
3461            'aznudefeet.com': 'https://cdn.aznudefeet.com/sparkthumbs/'
3462        };
3463        let host = '';
3464        try {
3465            host = new URL(video_html_link, location.href).hostname;
3466        } catch (e) { /* unparseable link: keep the page default */ }
3467        const site = Object.keys(sparksBaseByHost)
3468            .find(domain => host === domain || host.endsWith('.' + domain));
3469        const defaultBase = site ? sparksBaseByHost[site] : base_url_map.default;
3470        final_link = defaultBase +
3471            link_break[link_break.length - 1].replace('.html', '.mp4');
3472    }
3473    return final_link;
3474}
3475
3476function setupVideoPreviews(enablePreviews, sparksThumbUrl) {
3477    // const items = document.querySelectorAll('.video-list-item:not(.ad)');
3478
3479    // items.forEach(item => {
3480    //     const newItem = item.cloneNode(true);
3481    //     item.parentNode.replaceChild(newItem, item);
3482    // });
3483
3484    document.querySelectorAll('.video-list-item:not(.ad):not(.playlist-js)').forEach(item => {
3485        let video = null;
3486        let isTouchActive = false;
3487        let hoverTimeout = null;
3488        let stillHovering = false;
3489        let waitForReset = false;
3490
3491        const link = item.querySelector('a');
3492        // The React pages render a skeleton placeholder carrying this class
3493        // before they hydrate, and it holds no link. Without this guard the
3494        // addEventListener calls below throw, the throw escapes the forEach,
3495        // and every later item plus the .playlist-js loop below is skipped.
3496        if (!link) return;
3497
3498        function createVideo() {
3499            if (!enablePreviews || item.classList.contains('media-list-item--full')) return;
3500            if (item.querySelector('video')) return;
3501
3502            const videoSrc = getPreviewVideo(link.href, sparksThumbUrl);
3503            if (!videoSrc) return;
3504
3505            video = document.createElement('video');
3506            video.muted = true;
3507            video.loop = true;
3508            video.preload = 'none';
3509            video.playsInline = true;
3510
3511            const source = document.createElement('source');
3512            source.src = videoSrc;
3513            source.type = source.src.endsWith('.webm') ? 'video/webm' : 'video/mp4';
3514            video.appendChild(source);
3515            link.appendChild(video);
3516
3517            video.load();
3518
3519            video.onloadeddata = (e) => {
3520                if (!stillHovering) {
3521                    e.target.remove();
3522                    return;
3523                }
3524
3525                item.classList.remove('loading');
3526                item.classList.add('active');
3527
3528                e.target.play().then(() => item.classList.add('loaded'))
3529                    .catch(() => {
3530                        item.classList.remove('loaded', 'active', 'hiding-labels');
3531                        e.target.remove();
3532                    });
3533            };
3534
3535            video.onerror = () => {
3536                item.classList.remove('loading', 'active', 'hiding-labels');
3537                if (video) { video.remove(); video = null; }
3538            };
3539        }
3540
3541        function clearPreview() {
3542            stillHovering = false;
3543            item.classList.remove('hiding-labels');
3544
3545            if (hoverTimeout !== null) {
3546                clearTimeout(hoverTimeout);
3547                hoverTimeout = null;
3548            }
3549
3550            if (video) {
3551                video.onloadeddata = null;
3552                video.onerror = null;
3553                video.pause();
3554                video.remove();
3555                video = null;
3556            }
3557            item.classList.remove('loading', 'loaded', 'active');
3558        }
3559
3560        link.addEventListener('click', (e) => {
3561            clearPreview();
3562            waitForReset = true;
3563        });
3564
3565        link.addEventListener('mouseenter', () => {
3566            if (item.classList.contains('no-hover')) return;
3567            if (waitForReset) return;
3568            stillHovering = true;
3569            item.classList.add('hiding-labels');
3570            if (!video && !isTouchActive && enablePreviews && !item.classList.contains('media-list-item--full')) {
3571                item.classList.add('loading');
3572                hoverTimeout = setTimeout(createVideo, 1);
3573            }
3574        });
3575
3576        link.addEventListener('mouseleave', () => {
3577            if (item.classList.contains('no-hover')) return;
3578            clearPreview();
3579            waitForReset = false;
3580        });
3581
3582        link.addEventListener('touchstart', () => {
3583            if (waitForReset) return;
3584            if (item.classList.contains('no-hover')) return;
3585            if (!video && !isTouchActive && enablePreviews && !item.classList.contains('media-list-item--full')) {
3586                isTouchActive = true;
3587                stillHovering = true;
3588                item.classList.add('loading', 'hiding-labels');
3589                hoverTimeout = setTimeout(createVideo, 75);
3590            }
3591        });
3592
3593        link.addEventListener('touchend', () => {
3594            if (item.classList.contains('no-hover')) return;
3595            clearPreview();
3596            waitForReset = false;
3597            isTouchActive = false;
3598        });
3599    });
3600
3601    document.querySelectorAll('.video-list-item.playlist-js:not(.ad)').forEach(item => {
3602        let video = null;
3603        let isTouchActive = false;
3604        let hoverTimeout = null;
3605        let stillHovering = false;
3606        let waitForReset = false;
3607        let controls;
3608
3609        let parent_node = item.querySelector('a');
3610        let links = item.querySelector("img[preview-src]").getAttribute('preview-src');
3611        links = links.split(",");
3612        function createVideo() {
3613            let link;
3614            let preview_duration = 30000;
3615            let total_rotations = 10;
3616            let count = 0;
3617            function waitForVideoComplete(video, ms, callback) {
3618                let timeoutId;
3619                const handle = {
3620                    timeoutId: null, cancel: () => {
3621                        video.removeEventListener('ended', onComplete);
3622                        clearTimeout(timeoutId);
3623                    }
3624                };
3625                const onComplete = () => {
3626                    video.removeEventListener('ended', onComplete);
3627                    clearTimeout(timeoutId);
3628                    callback('ended', handle);
3629                };
3630                video.addEventListener('ended', onComplete);
3631                timeoutId = setTimeout(() => {
3632                    video.removeEventListener('ended', onComplete);
3633                    callback('timeout: ' + timeoutId, handle);
3634                }, ms);
3635                handle.timeoutId = timeoutId;
3636                return handle;
3637            }
3638
3639            function get_next_video_element() {
3640                link = links[0];
3641                links.push(links.shift());
3642                if (!enablePreviews || item.classList.contains('media-list-item--full')) return null;
3643                const videoSrc = getPreviewVideo(link, sparksThumbUrl);
3644                if (!videoSrc) return;
3645                video = document.createElement('video');
3646                video.muted = true;
3647                video.loop = false;
3648                video.preload = 'none';
3649                video.playsInline = true;
3650                const source = document.createElement('source');
3651                source.src = videoSrc;
3652                source.type = source.src.endsWith('.webm') ? 'video/webm' : 'video/mp4';
3653                video.appendChild(source);
3654                video.load();
3655                video.onloadeddata = (e) => {
3656                    if (!stillHovering) {
3657                        e.target.remove();
3658                        return;
3659                    }
3660                    item.classList.remove('loading');
3661                    item.classList.add('active');
3662                    e.target.play().then(() => item.classList.add('loaded'))
3663                        .catch(() => {
3664                            item.classList.remove('loaded', 'active', 'hiding-labels');
3665                            e.target.remove();
3666                        });
3667                };
3668                video.onerror = () => {
3669                    item.classList.remove('loading', 'active', 'hiding-labels');
3670                    if (video) {
3671                        video.remove();
3672                        video = null;
3673                    }
3674                };
3675                return video;
3676            }
3677
3678            function previewNextVideo() {
3679                video = get_next_video_element();
3680                if (!video) return null;
3681                if (count === 0 && $(parent_node).find('video').length === 0) parent_node.appendChild(video);
3682                else $(parent_node).find('video').replaceWith(video);
3683                controls = waitForVideoComplete(video, preview_duration,
3684                    (result, cancel) => {
3685                        if (count < total_rotations && stillHovering) {
3686                            cancel.cancel();
3687                            previewNextVideo();
3688                        } else {
3689                            cancel.cancel();
3690                        }
3691                    });
3692            }
3693            previewNextVideo();
3694        }
3695
3696        function clearPreview() {
3697            stillHovering = false;
3698            item.classList.remove('hiding-labels');
3699
3700            if (hoverTimeout !== null) {
3701                clearTimeout(hoverTimeout);
3702                hoverTimeout = null;
3703            }
3704            if (controls) controls.cancel();
3705            if (video) {
3706                video.onloadeddata = null;
3707                video.onerror = null;
3708                video.pause();
3709                video.remove();
3710                video = null;
3711            }
3712            item.classList.remove('loading', 'loaded', 'active');
3713        }
3714
3715        parent_node.addEventListener('click', (e) => {
3716            clearPreview();
3717            waitForReset = true;
3718        })
3719
3720        parent_node.addEventListener('mouseenter', () => {
3721            if (item.classList.contains('no-hover')) return;
3722            if (waitForReset) return;
3723            stillHovering = true;
3724            item.classList.add('hiding-labels');
3725            if (!video && !isTouchActive && enablePreviews && !item.classList.contains('media-list-item--full')) {
3726                item.classList.add('loading');
3727                hoverTimeout = setTimeout(createVideo, 1);
3728            }
3729        });
3730
3731        parent_node.addEventListener('mouseleave', () => {
3732            if (item.classList.contains('no-hover')) return;
3733            clearPreview();
3734            waitForReset = false;
3735        });
3736
3737        parent_node.addEventListener('touchstart', () => {
3738            if (waitForReset) return;
3739            if (item.classList.contains('no-hover')) return;
3740            if (!video && !isTouchActive && enablePreviews && !item.classList.contains('media-list-item--full')) {
3741                isTouchActive = true;
3742                stillHovering = true;
3743                item.classList.add('loading', 'hiding-labels');
3744                hoverTimeout = setTimeout(createVideo, 75);
3745            }
3746        });
3747
3748        parent_node.addEventListener('touchend', () => {
3749            if (item.classList.contains('no-hover')) return;
3750            clearPreview();
3751            waitForReset = false;
3752            isTouchActive = false;
3753        });
3754    });
3755
3756}
3757
3758document.addEventListener('DOMContentLoaded', () => {
3759    setupVideoPreviews(varValues.shouldShowVideoPreviews);
3760
3761    const tabs = document.querySelectorAll('.videos-tab');
3762    const tabPanes = document.querySelectorAll('.videos-tab-pane');
3763
3764    if(tabs && tabPanes){
3765        tabs.forEach(tab => {
3766            tab.addEventListener('click', () => {
3767                tabs.forEach(t => t.classList.remove('active'));
3768                tabPanes.forEach(pane => pane.classList.remove('active'));
3769
3770                tab.classList.add('active');
3771                const tabId = tab.getAttribute('data-tab');
3772                document.getElementById(tabId).classList.add('active');
3773            });
3774        });
3775    }
3776});
3777
3778
3779function setupSlider(wrapper) {
3780    if (!wrapper || wrapper.__sliderInit) return;
3781    wrapper.__sliderInit = true;
3782
3783    var viewport = wrapper.querySelector('.media-slider-viewport');
3784    var list = wrapper.querySelector('.media-list');
3785    var leftBtn = wrapper.querySelector('.media-list--slider-btn-left');
3786    var rightBtn = wrapper.querySelector('.media-list--slider-btn-right');
3787
3788    function clamp(v, min, max) {return Math.max(min, Math.min(max, v));}
3789
3790    function getStep() {
3791        var first = list.querySelector('.media-list-item');
3792        if (!first) return viewport.clientWidth * 0.9;
3793        var style = window.getComputedStyle(list);
3794        var gap = parseFloat(style.columnGap || style.gap || 10);
3795        return first.getBoundingClientRect().width + gap;
3796    }
3797
3798    function maxScrollLeft() {return Math.max(0, viewport.scrollWidth - viewport.clientWidth - 1);}
3799
3800    function updateArrows() {
3801        var maxScroll = maxScrollLeft();
3802        leftBtn.disabled = viewport.scrollLeft <= 1;
3803        rightBtn.disabled = viewport.scrollLeft >= maxScroll;
3804    }
3805
3806    function scrollByStep(dir) {
3807        var next = viewport.scrollLeft + dir * getStep();
3808        var maxLeft = maxScrollLeft();
3809        viewport.scrollTo({ left: clamp(next, 0, maxLeft), behavior: 'smooth' });
3810    }
3811
3812    function setButtonsTop() {
3813        var img = list.querySelector('.media-list-item img');
3814        if (!img) return;
3815        var wrapRect = wrapper.getBoundingClientRect();
3816        var imgRect = img.getBoundingClientRect();
3817        var center = imgRect.top - wrapRect.top + imgRect.height / 2;
3818        wrapper.style.setProperty('--slider-btn-top', center + 'px');
3819    }
3820
3821    leftBtn.addEventListener('click', function () {scrollByStep(-1);});
3822    rightBtn.addEventListener('click', function () {scrollByStep(1);});
3823
3824    viewport.addEventListener('keydown', function (e) {
3825        if (e.key === 'ArrowRight') {e.preventDefault(); scrollByStep(1);}
3826        if (e.key === 'ArrowLeft') {e.preventDefault(); scrollByStep(-1);}
3827    });
3828
3829    var isDown = false, startX = 0, startLeft = 0, moved = false;
3830    viewport.addEventListener('mousedown', function (e) {
3831        isDown = true;
3832        moved = false;
3833        startX = e.clientX;
3834        startLeft = viewport.scrollLeft;
3835        viewport.classList.add('dragging');
3836    });
3837
3838    window.addEventListener('mouseup', function () {
3839        isDown = false;
3840        viewport.classList.remove('dragging');
3841    });
3842
3843    window.addEventListener('mousemove', function (e) {
3844        if (!isDown) return;
3845        var dx = e.clientX - startX;
3846        if (Math.abs(dx) > 3) moved = true;
3847        viewport.scrollLeft = startLeft - dx;
3848    });
3849
3850    viewport.addEventListener('click', function (e) {
3851        if (moved) e.preventDefault();
3852    }, true);
3853
3854    viewport.addEventListener('scroll', updateArrows, { passive: true });
3855    window.addEventListener('resize', function () {
3856        updateArrows();
3857        setButtonsTop();
3858    });
3859
3860    var imgs = list.querySelectorAll('img');
3861    var pending = imgs.length;
3862    if (pending === 0) {
3863        requestAnimationFrame(function () {
3864            updateArrows();
3865            setButtonsTop();
3866        });
3867    } else {
3868        imgs.forEach(function (img) {
3869            if (img.complete && img.naturalWidth) {
3870                if (--pending === 0) {
3871                    updateArrows();
3872                    setButtonsTop();
3873                }
3874            } else {
3875                img.addEventListener('load', function () {
3876                    if (--pending === 0) {
3877                        updateArrows();
3878                        setButtonsTop();
3879                    }
3880                }, { once: true });
3881                img.addEventListener('error', function () {
3882                    if (--pending === 0) {
3883                        updateArrows();
3884                        setButtonsTop();
3885                    }
3886                }, { once: true });
3887            }
3888        });
3889    }
3890
3891    if ('ResizeObserver' in window) {
3892        var ro = new ResizeObserver(function () {
3893            updateArrows();
3894            setButtonsTop();
3895        });
3896        ro.observe(list);
3897        ro.observe(viewport);
3898    }
3899
3900    requestAnimationFrame(function () {
3901        updateArrows();
3902        setButtonsTop();
3903    });
3904    setTimeout(function () {
3905        updateArrows();
3906        setButtonsTop();
3907    }, 0);
3908}
3909
3910(function (){
3911    document.addEventListener('DOMContentLoaded', function () {
3912        var wrappers = document.querySelectorAll('.media-slider-wrapper');
3913        for (var i = 0; i < wrappers.length; i++) setupSlider(wrappers[i]);
3914    });
3915})();
3916
3917
3918
3919// NEW PAGINATION SCRIPTS
3920function resizePagination() {
3921    const paginations = document.querySelectorAll('.pagination-section');
3922    if (!paginations.length) return;
3923
3924    paginations.forEach(pagination => {
3925        pagination.querySelectorAll('li').forEach(item => {
3926            item.style.display = 'inline-block';
3927        });
3928
3929        const activeItem = pagination.querySelector('.section-page-item.active');
3930        const containerWidth = pagination.parentElement.parentElement.clientWidth;
3931        const items = Array.from(pagination.querySelectorAll('li'));
3932        const gap = 8;
3933
3934        let totalWidth = items.reduce((acc, curr) => {
3935            const style = window.getComputedStyle(curr);
3936            return acc + curr.clientWidth + parseFloat(style.marginLeft) + parseFloat(style.marginRight) + gap;
3937        }, 0);
3938
3939        if (totalWidth > containerWidth) {
3940            let availableSpace = containerWidth;
3941            const fixedButtons = pagination.querySelectorAll('li.section-next, li.section-last-page-item, li.section-pagination-rest');
3942            const priorityButtons = [activeItem, activeItem?.previousElementSibling, activeItem?.nextElementSibling].filter(
3942Boolean);
3943
3944            const fixedAndPriorityWidth = [...fixedButtons, ...priorityButtons].reduce((acc, curr) => {
3945                if (curr) {
3946                    const style = window.getComputedStyle(curr);
3947                    acc += curr.clientWidth + parseFloat(style.marginLeft) + parseFloat(style.marginRight) + gap;
3948                }
3949                return acc;
3950            }, 0);
3951
3952            availableSpace -= fixedAndPriorityWidth;
3953
3954            const adjustableItems = Array.from(pagination.querySelectorAll('.section-page-item:not(.section-last-page-item, .active)'))
3955                .filter(item => !priorityButtons.includes(item))
3956                .sort((a, b) => {
3957                    const aPage = parseInt(a.querySelector('.section-page-link span').textContent);
3958                    const bPage = parseInt(b.querySelector('.section-page-link span').textContent);
3959                    return bPage - aPage;
3960                });
3961
3962            for (const item of adjustableItems) {
3963                if (availableSpace <= 0) {
3964                    item.style.display = 'none';
3965                } else {
3966                    const itemStyle = window.getComputedStyle(item);
3967                    const itemFullWidth = item.clientWidth + parseFloat(itemStyle.marginLeft) + parseFloat(itemStyle.marginRight) + gap;
3968                    if (availableSpace >= itemFullWidth) {
3969                        availableSpace -= itemFullWidth;
3970                    } else {
3971                        item.style.display = 'none';
3972                    }
3973                }
3974            }
3975        }
3976    });
3977}
3978
3979resizePagination();
3980
3981window.addEventListener('resize', resizePagination);
3982
3983// NEW TAGS FOOTER SCRIPTS
3984const tagsFooterContainer = document.getElementById('tagsFooterContainer');
3985const viewMoreContainer = document.getElementById('viewMoreContainer');
3986const viewMoreBtn = document.getElementById('viewMoreBtn');
3987
3988if(tagsFooterContainer){
3989    let initialMaxHeight = window.innerWidth <= 600 ? 185 : 110;
3990    tagsFooterContainer.style.maxHeight = initialMaxHeight - 3 + 'px';
3991
3992    function checkOverflow() {
3993        const hasOverflow = tagsFooterContainer.scrollHeight > tagsFooterContainer.clientHeight;
3994        if (hasOverflow) {
3995            viewMoreContainer.classList.add('visible');
3996        } else {
3997            viewMoreContainer.classList.remove('visible');
3998            tagsFooterContainer.classList.remove('expanded');
3999            viewMoreBtn.textContent = 'More ▼';
4000        }
4001    }
4002
4003    viewMoreBtn.addEventListener('click', () => {
4004        tagsFooterContainer.classList.toggle('expanded');
4005        if (tagsFooterContainer.classList.contains('expanded')) {
4006            const fullHeight = tagsFooterContainer.scrollHeight;
4007            tagsFooterContainer.style.maxHeight = fullHeight + 'px';
4008            viewMoreBtn.textContent = 'Less ▲';
4009        } else {
4010            tagsFooterContainer.style.maxHeight = initialMaxHeight - 3 + 'px';
4011            viewMoreBtn.textContent = 'More ▼';
4012        }
4013    });
4014
4015    window.addEventListener('load', () => {
4016        checkOverflow();
4017    });
4018
4019    window.addEventListener('resize', () => {
4020        initialMaxHeight = window.innerWidth <= 600 ? 185 : 110;
4021        if (!tagsFooterContainer.classList.contains('expanded')) {
4022            tagsFooterContainer.style.maxHeight = initialMaxHeight - 3 + 'px';
4023        }
4024        checkOverflow();
4025    });
4026}
4027
4028// NEW SINGLE VIDEO PAGE
4029const shareBtn = document.querySelector('.single-video-share-btn');
4030const shareModal = document.getElementById('single-video-share-modal');
4031const closeShareModal = document.querySelector('.single-video-close-modal');
4032const favoriteBtn = document.querySelector('.single-video-favorite-btn');
4033const toggleButton = document.querySelector('.toggle-button');
4034const movieInfoContent = document.querySelector('.movie-info-content');
4035
4036if (shareModal) {
4037    shareBtn.addEventListener('click', () => {
4038        shareModal.style.display = 'flex';
4039        shareModal.showModal();
4040    });
4041
4042    closeShareModal.addEventListener('click', () => {
4043        shareModal.close();
4044        shareModal.style.display = 'none';
4045    });
4046
4047    shareModal.addEventListener('click', (e) => {
4048        const rect = shareModal.querySelector('.single-video-modal-content').getBoundingClientRect();
4049        const isInDialog = (rect.top <= e.clientY && e.clientY <= rect.bottom &&
4050            rect.left <= e.clientX && e.clientX <= rect.right);
4051        if (!isInDialog) {
4052            shareModal.close();
4053            shareModal.style.display = 'none';
4054        }
4055    });
4056}
4057
4058if(favoriteBtn){
4059    favoriteBtn.addEventListener('click', () => {
4060        favoriteBtn.classList.toggle('favorited');
4061    });
4062}
4063
4064if(toggleButton){
4065    toggleButton.addEventListener('click', () => {
4066        movieInfoContent.classList.toggle('visible');
4067        const span = toggleButton.querySelector('span');
4068        span.textContent = movieInfoContent.classList.contains('visible') ?'Less' : 'More';
4069    });
4070}
4071
4072document.querySelectorAll('.single-video-input-group').forEach(group => {
4073    const input = group.querySelector('input');
4074
4075    UniversalTooltip.attach(group, {
4076        trigger: 'click',
4077        placement: 'top',
4078        content: 'Copied!',
4079        autoHide: 1000,
4080    });
4081
4082    const copyText = async () => {
4083        try {
4084            await navigator.clipboard.writeText(input.value);
4085        } catch (err) {
4086            console.error('Failed to copy:', err);
4087        }
4088    };
4089
4090    group.addEventListener('click', copyText);
4091});
4092
4093//region NEW FILTERS SECTION
4094document.addEventListener('DOMContentLoaded', function() {
4095    if (!document.getElementById('sort-button')) return;
4096    const browseFilterToggle = document.querySelector('.browse-filter-toggle');
4097    const browseFilterColumn = document.querySelector('.browse-filter-column');
4098    const browseFilterOverlay = document.querySelector('.browse-filter-overlay');
4099    const defaultText = document.getElementById('sort-button').firstElementChild.innerHTML;
4100    if (browseFilterToggle) {
4101        browseFilterToggle.addEventListener('click', () => {
4102            browseFilterColumn.classList.toggle('active');
4103            browseFilterOverlay.classList.toggle('active');
4104        });
4105    }
4106    if (browseFilterOverlay) {
4107        browseFilterOverlay.addEventListener('click', () => {
4108            if (browseFilterColumn) browseFilterColumn.classList.remove('active');
4109            browseFilterOverlay.classList.remove('active');
4110        });
4111    }
4112
4113    // Filter section interactions
4114    document.querySelectorAll('.browse-filter-section label:not(.tag-label)').forEach(label => {
4115        label.addEventListener('click', () => {
4116            label.classList.toggle('active');
4117        });
4118    });
4119    document.querySelectorAll('.tag-label').forEach(label => {
4120        label.addEventListener('click', (e) => {
4121            if (e.target.classList.contains('hide-button') || e.target.parentElement.classList.contains('hide-button')) {
4122                return;
4123            }
4124            if (e.target.classList.contains('plus-symbol')) {
4125                return;
4126            }
4127            if (e.target.classList.contains('browse-filter-text')) {
4128                label.classList.toggle('active');
4129            }
4130        });
4131    });
4132
4133
4134    //region Tooltip functionality */
4135    // UniversalTooltip.attach('.slider-thumb',
4136    //     {trigger: 'hover', placement: 'top', content: 'Drag to set filter'});
4137    // UniversalTooltip.attach('.browse-filter-column .plus-symbol',
4138    //     {trigger: 'hover', placement: 'top', content: 'Combine tag'});
4139    // UniversalTooltip.attach('.browse-filter-column .hide-button span',
4140    //     {trigger: 'hover', placement: 'top', content: 'Hide tag'});
4141    //endregion */
4142
4143    //region Slider */
4144    const thumbMin = document.getElementById('thumb-min');
4145    const thumbMax = document.getElementById('thumb-max');
4146    const sliderRange = document.querySelector('.slider-range');
4147    const sliderTrack = document.querySelector('.slider-track');
4148    const positions = [0, 25, 50, 75, 100];
4149    let minIndex = 0;
4150    let maxIndex = 4;
4151
4152    function updateSliderRange() {
4153        thumbMin.style.left = `${positions[minIndex]}%`;
4154        thumbMax.style.left = `${positions[maxIndex]}%`;
4155        sliderRange.style.left = `${positions[minIndex]}%`;
4156        sliderRange.style.width = `${positions[maxIndex] - positions[minIndex]}%`;
4157    }
4158
4159    function dragThumb(thumb, isMin) {
4160        let isDragging = false;
4161
4162        thumb.addEventListener('mousedown', (e) => {
4163            e.preventDefault();
4164            isDragging = true;
4165            document.addEventListener('mousemove', onMouseMove);
4166            document.addEventListener('mouseup', onMouseUp);
4167        });
4168
4169        function onMouseMove(e) {
4170            if (!isDragging) return;
4171
4172            const sliderRect = sliderTrack.getBoundingClientRect();
4173            const trackWidth = sliderRect.width;
4174            const relativeX = e.clientX - sliderRect.left;
4175            const percentage = (relativeX / trackWidth) * 100;
4176
4177            let closestIndex = 0;
4178            let minDiff = Infinity;
4179            positions.forEach((pos, index) => {
4180                const diff = Math.abs(pos - percentage);
4181                if (diff < minDiff) {
4182                    minDiff = diff;
4183                    closestIndex = index;
4184                }
4185            });
4186
4187            if (isMin) {
4188                if (closestIndex >= maxIndex) {
4189                    closestIndex = maxIndex - 1;
4190                }
4191                minIndex = closestIndex;
4192            } else {
4193                if (closestIndex <= minIndex) {
4194                    closestIndex = minIndex + 1;
4195                }
4196                maxIndex = closestIndex;
4197            }
4198
4199            updateSliderRange();
4200        }
4201
4202        function onMouseUp() {
4203            isDragging = false;
4204            document.removeEventListener('mousemove', onMouseMove);
4205            document.removeEventListener('mouseup', onMouseUp);
4206        }
4207
4208        thumb.addEventListener('touchstart', (e) => {
4209            e.preventDefault();
4210            isDragging = true;
4211            document.addEventListener('touchmove', onTouchMove);
4212            document.addEventListener('touchend', onTouchEnd);
4213        });
4214
4215        function onTouchMove(e) {
4216            if (!isDragging) return;
4217
4218            const sliderRect = sliderTrack.getBoundingClientRect();
4219            const trackWidth = sliderRect.width;
4220            const relativeX = e.touches[0].clientX - sliderRect.left;
4221            const percentage = (relativeX / trackWidth) * 100;
4222
4223            let closestIndex = 0;
4224            let minDiff = Infinity;
4225            positions.forEach((pos, index) => {
4226                const diff = Math.abs(pos - percentage);
4227                if (diff < minDiff) {
4228                    minDiff = diff;
4229                    closestIndex = index;
4230                }
4231            });
4232
4233            if (isMin) {
4234                if (closestIndex >= maxIndex) {
4235                    closestIndex = maxIndex - 1;
4236                }
4237                minIndex = closestIndex;
4238            } else {
4239                if (closestIndex <= minIndex) {
4240                    closestIndex = minIndex + 1;
4241                }
4242                maxIndex = closestIndex;
4243            }
4244
4245            updateSliderRange();
4246        }
4247
4248        function onTouchEnd() {
4249            isDragging = false;
4250            document.removeEventListener('touchmove', onTouchMove);
4251            document.removeEventListener('touchend', onTouchEnd);
4252        }
4253    }
4254
4255    if(sliderTrack){
4256        sliderTrack.addEventListener('click', (e) => {
4257            const sliderRect = sliderTrack.getBoundingClientRect();
4258            const trackWidth = sliderRect.width;
4259            const relativeX = e.clientX - sliderRect.left;
4260            const percentage = (relativeX / trackWidth) * 100;
4261
4262            let closestIndex = 0;
4263            let minDiff = Infinity;
4264            positions.forEach((pos, index) => {
4265                const diff = Math.abs(pos - percentage);
4266                if (diff < minDiff) {
4267                    minDiff = diff;
4268                    closestIndex = index;
4269                }
4270            });
4271
4272            const minDistance = Math.abs(positions[minIndex] - percentage);
4273            const maxDistance = Math.abs(positions[maxIndex] - percentage);
4274
4275            if (minDistance <= maxDistance) {
4276                if (closestIndex >= maxIndex) {
4277                    minIndex = maxIndex - 1;
4278                } else {
4279                    minIndex = closestIndex;
4280                }
4281            } else {
4282                if (closestIndex <= minIndex) {
4283                    maxIndex = minIndex + 1;
4284                } else {
4285                    maxIndex = closestIndex;
4286                }
4287            }
4288
4289            updateSliderRange();
4290        });
4291
4292        dragThumb(thumbMin, true);
4293        dragThumb(thumbMax, false);
4294
4295        updateSliderRange();
4296
4297        window.addEventListener('resize', updateSliderRange);
4298    }
4299    //endregion ^^^^ Slider ^^^^ */
4300
4301
4302    //region Dropdown Menu */
4303    document.querySelectorAll('.sort-dropdown-wrapper').forEach(e => {
4304        let menu_button = e.querySelector('.sort-button');
4305        menu_button.addEventListener("click", (e) => {
4306            if (!menu_button.parentElement.querySelector('.sort-dropdown').classList.contains('sort-button-off')) {
4307                close_dropdown_menus();
4308            } else {
4309                close_dropdown_menus();
4310                menu_button.parentElement.querySelector('.sort-dropdown').classList.toggle('sort-button-off');
4311            }
4312        });
4313
4314        e.querySelectorAll('.browse-filter-item').forEach(i => {
4315            i.addEventListener('click', (c) => {
4316                if (i.classList.contains('selected')){
4317                    c.preventDefault();
4318                    return;
4319                }
4320                if (i.classList.contains('submenu')){
4321                    c.preventDefault();
4322                }
4323                if (e.id === 'sort_menu') {
4324                    hide_other_menus(e);
4325                }
4326                reset_menu_selection(e);
4327                if (!i.classList.contains('selected')) {
4328                    i.classList.add('selected');
4329                }
4330                e.querySelector('.sort-button').firstElementChild.innerHTML = c.target.innerText;
4331
4332                if (i.hasAttribute('submenu')) {
4333                    document.getElementById(i.getAttribute('submenu')).classList.toggle('menu-active');
4334                }
4335                close_dropdown_menus();
4336            });
4337        });
4338    })
4339
4340    function close_dropdown_menus() {
4341        document.querySelectorAll('.sort-dropdown').forEach(e => {
4342            e.classList.add('sort-button-off');
4343        })
4344    }
4345
4346    function hide_other_menus(current_menu) {
4347        document.querySelectorAll('.sort-dropdown-wrapper').forEach(e => {
4348            if (e.id !== current_menu.id && e.id !== 'sort_menu') e.classList.remove('menu-active');
4349        });
4350    }
4351
4352    function reset_menu_selection(parentElement) {
4353        Array.from(parentElement.querySelectorAll('.browse-filter-item')).forEac
4353h(child => {
4354            child.classList.remove('selected');
4355        });
4356    }
4357
4358    document.addEventListener("click", function(e) {
4359        const sortButton = e.target.closest('.sort-button');
4360        const selectedItem = e.target.closest('.browse-filter-item');
4361        if (sortButton || selectedItem) {
4362            return;
4363        }
4364        close_dropdown_menus();
4365    });
4366
4367    //endregion ^^^^ Dropdown Menu ^^^^ */
4368});
4369//endregion
4370
4371(function () {
4372    const load = src => new Promise((res, rej) => {
4373        const s = document.createElement('script');
4374        s.src = src; s.async = false; s.defer = true;
4375        s.onload = res; s.onerror = rej; document.head.appendChild(s);
4376    });
4377    const ready = (pred, t=5000, step=50) => new Promise((res, rej) => {
4378        const end = Date.now()+t; (function tick(){
4379            try { if (pred()) return res(); } catch {}
4380            return Date.now()>end ? rej(new Error('timeout')) : setTimeout(tick, step);
4381        })();
4382    });
4383
4384    window.AdProvider = window.AdProvider || [];
4385
4386    (async () => {
4387        await load('https://a.magsrv.com/ad-provider.js');
4388        await ready(() => typeof window.AdProvider.push === 'function');
4389        await load('https://a.pemsrv.com/ad-provider.js');
4390        window.AdProvider.push({ serve: {} });
4391    })();
4392})();
4393
4394// Popup Ads
4395const isChromium=!!window.chrome;
4396const isMobile=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
4397
4398(function(){
4399    const nativeID = isMobile ? varValues.popupAd.native_id_mobile : varValues.popupAd.native_id_desktop;
4400    const ins = document.createElement("ins");
4401    let combined_tags = getKeywords();
4402    // ExoClick's current "Get code" for the native zones uses eas6a97888e20.
4403    // eas6a97888e was the older tag. The interstitial below keeps its own class
4404    // (eas6a97888e33) and the outstream keeps eas6a97888e37 - different zones.
4405    ins.className="eas6a97888e20";
4406    ins.setAttribute("data-zoneid",nativeID);
4407    ins.setAttribute("data-keywords", combined_tags);
4408    if(document.body.classList.contains('agego-unverified')) {
4409        ins.setAttribute("data-ex_av", "2");
4410        ins.setAttribute("data-block-ad-types", "101");
4411    } else if(document.body.classList.contains('agego-verified') || document.body.classList.contains('agego-legacy')) {
4412        ins.setAttribute("data-ex_av", "1");
4413    }
4414    if(document.getElementById(varValues.popupAd.native_tag)){
4415        document.getElementById(varValues.popupAd.native_tag).appendChild(ins);
4416    }
4417
4418    const root = document.getElementById('react-root');
4419    const noadpages = ['search'];
4420    if (root && noadpages.includes(root.dataset.page)) return;
4421
4422    if(isChromium){
4423        const interId = isMobile ? varValues.popupAd.inter_id_mobile : varValues.popupAd.inter_id_desktop;
4424        const ins = document.createElement("ins");
4425        ins.className = "eas6a97888e33";
4426        ins.setAttribute("data-zoneid", interId);
4427        ins.setAttribute("data-keywords", combined_tags);
4428        if(document.body.classList.contains('agego-unverified')) {
4429            ins.setAttribute("data-ex_av", "2");
4430            ins.setAttribute("data-block-ad-types", "101");
4431        } else if(document.body.classList.contains('agego-verified') || document.body.classList.contains('agego-legacy')) {
4432            ins.setAttribute("data-ex_av", "1");
4433        }
4434        return document.body.appendChild(ins);
4435    }
4436
4437    // let customTargeting = {};
4438    // if(document.body.classList.contains('agego-unverified')) {
4439    //     customTargeting = { ex_av: '2', block_ad_types: '101' };
4440    // } else if(document.body.classList.contains('agego-verified') || document.body.classList.contains('agego-legacy')) {
4441    //     customTargeting = { ex_av: '1' };
4442    // }
4443
4444    const adConfig = {
4445        "ads_host": "a.pemsrv.com",
4446        "syndication_host": "s.pemsrv.com",
4447        "idzone": varValues.popupAd.pops_id,
4448        "popup_fallback": false,
4449        "popup_force": false,
4450        "chrome_enabled": false,
4451        "new_tab": false,
4452        "frequency_period": 60,
4453        "frequency_count": 1,
4454        "trigger_method": 4,
4455        "trigger_class": "noexopop",
4456        "trigger_delay": 0,
4457        "capping_enabled": true,
4458        "tcf_enabled": true,
4459        "only_inline": false,
4460        "tags": combined_tags,
4461        // "customTargeting": customTargeting
4462    };
4463
4464    window.document.querySelectorAll||(document.querySelectorAll=document.body.querySelectorAll=Object.querySelectorAll=function(e,o,t,i,n){var r=document,a=r.createStyleSheet();for(n=r.all,o=[],t=(e=e.replace(/\[for\b/gi,"[htmlFor").split(",")).length;t--;){for(a.addRule(e[t],"k:v"),i=n.length;i--;)n[i].currentStyle.k&&o.push(n[i]);a.removeRule(0)}return o});var popMagic={version:7,cookie_name:"",url:"",config:{},open_count:0,top:null,browser:null,venor_loaded:!1,venor:!1,tcfData:null,configTpl:{ads_host:"",syndication_host:"",idzone:"",frequency_period:720,frequency_count:1,trigger_method:1,trigger_class:"",popup_force:!1,popup_fallback:!1,chrome_enabled:!0,new_tab:!1,cat:"",tags:"",el:"",sub:"",sub2:"",sub3:"",only_inline:!1,trigger_delay:0,capping_enabled:!0,tcf_enabled:!1,cookieconsent:!0,should_fire:function(){return!0},on_redirect:null},init:function(e){if(void 0!==e.idzone&&e.idzone){void 0===e.customTargeting&&(e.customTargeting=[]),window.customTargeting=e.customTargeting||null;var o=Object.keys(e.customTargeting).filter(function(e){return e.search("ex_")>=0});for(var t in o.length&&o.forEach(function(e){return this.configTpl[e]=null}.bind(this)),this.configTpl)Object.prototype.hasOwnProperty.call(this.configTpl,t)&&(void 0!==e[t]?this.config[t]=e[t]:this.config[t]=this.configTpl[t]);if(void 0!==this.config.idzone&&""!==this.config.idzone){!0!==this.config.only_inline&&this.loadHosted();var i=this;this.checkTCFConsent(function(){"complete"===document.readyState?i.preparePopWait():i.addEventToElement(window,"load",i.preparePop)})}}},getCountFromCookie:function(){if(!this.config.cookieconsent)return 0;var e=popMagic.getCookie(popMagic.cookie_name),o=void 0===e?0:parseInt(e);return isNaN(o)&&(o=0),o},getLastOpenedTimeFromCookie:function(){var e=popMagic.getCookie(popMagic.cookie_name),o=null;if(void 0!==e){var t=e.split(";")[1];o=t>0?parseInt(t):0}return isNaN(o)&&(o=null),o},shouldShow:function(e){if(e=e||!1,!popMagic.config.capping_enabled){var o=!0,t=popMagic.config.should_fire;try{e||"function"!=typeof t||(o=Boolean(t()))}catch(e){console.error("Error executing should fire callback function:",e)}return o&&0===popMagic.open_count}if(popMagic.open_count>=popMagic.config.frequency_count)return!1;var i=popMagic.getCountFromCookie(),n=popMagic.getLastOpenedTimeFromCookie(),r=Math.floor(Date.now()/1e3),a=n+popMagic.config.trigger_delay;return!(n&&a>r)&&(popMagic.open_count=i,!(i>=popMagic.config.frequency_count))},venorShouldShow:function(){return popMagic.venor_loaded&&"0"===popMagic.venor},setAsOpened:function(e){var o=e?e.target||e.srcElement:null,t={id:"",tagName:"",classes:"",text:"",href:"",elm:""};void 0!==o&&null!=o&&(t={id:void 0!==o.id&&null!=o.id?o.id:"",tagName:void 0!==o.tagName&&null!=o.tagName?o.tagName:"",classes:void 0!==o.classList&&null!=o.classList?o.classList:"",text:void 0!==o.outerText&&null!=o.outerText?o.outerText:"",href:void 0!==o.href&&null!=o.href?o.href:"",elm:o});var i=new CustomEvent("creativeDisplayed-"+popMagic.config.idzone,{detail:t});if(document.dispatchEvent(i),popMagic.config.capping_enabled){var n=1;n=0!==popMagic.open_count?popMagic.open_count+1:popMagic.getCountFromCookie()+1;var r=Math.floor(Date.now()/1e3);popMagic.config.cookieconsent&&popMagic.setCookie(popMagic.cookie_name,n+";"+r,popMagic.config.frequency_period)}else++popMagic.open_count},loadHosted:function(){var e=document.createElement("script");for(var o in e.type="application/javascript",e.async=!0,e.src="//"+this.config.ads_host+"/popunder1000.js",e.id="popmagicldr",this.config)Object.prototype.hasOwnProperty.call(this.config,o)&&"ads_host"!==o&&"syndication_host"!==o&&e.setAttribute("data-exo-"+o,this.config[o]);var t=document.getElementsByTagName("body").item(0);t.firstChild?t.insertBefore(e,t.firstChild):t.appendChild(e)},preparePopWait:function(){setTimeout(popMagic.preparePop,400)},preparePop:function(){if("object"!=typeof exoJsPop101||!Object.prototype.hasOwnProperty.call(exoJsPop101,"add")){if(popMagic.top=self,popMagic.top!==self)try{top.document.location.toString()&&(popMagic.top=top)}catch(e){}if(popMagic.cookie_name="zone-cap-"+popMagic.config.idzone,popMagic.config.capping_enabled||(document.cookie=popMagic.cookie_name+"=;expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"),popMagic.shouldShow(!0)){var e=new XMLHttpRequest;e.onreadystatechange=function(){e.readyState==XMLHttpRequest.DONE&&(popMagic.venor_loaded=!0,200==e.status?popMagic.venor=e.responseText:popMagic.venor="0")};var o="https:"!==document.location.protocol&&"http:"!==document.location.protocol?"https:":document.location.protocol;e.open("GET",o+"//"+popMagic.config.syndication_host+"/venor.php",!0);try{e.send()}catch(e){popMagic.venor_loaded=!0}}if(popMagic.buildUrl(),popMagic.browser=popMagic.browserDetector.getBrowserInfo(),popMagic.config.chrome_enabled||!popMagic.browser.isChrome){var t=popMagic.getPopMethod(popMagic.browser);popMagic.addEvent("click",t)}}},getPopMethod:function(e){return popMagic.config.popup_force||popMagic.config.popup_fallback&&e.isChrome&&e.version>=68&&!e.isMobile?popMagic.methods.popup:e.isMobile?popMagic.methods.default:e.isChrome?popMagic.methods.chromeTab:popMagic.methods.default},checkTCFConsent:function(e){if(this.config.tcf_enabled&&"function"==typeof window.__tcfapi){var o=this;window.__tcfapi("addEventListener",2,function(t,i){i&&(o.tcfData=t,"tcloaded"!==t.eventStatus&&"useractioncomplete"!==t.eventStatus||(window.__tcfapi("removeEventListener",2,function(){},t.listenerId),e()))})}else e()},buildUrl:function(){var e,o="https:"!==document.location.protocol&&"http:"!==document.location.protocol?"https:":document.location.protocol,t=top===self?document.URL:document.referrer,i={type:"inline",name:"popMagic",ver:this.version},n="";customTargeting&&Object.keys(customTargeting).length&&("object"==typeof customTargeting?Object.keys(customTargeting):customTargeting).forEach(function(o){"object"==typeof customTargeting?e=customTargeting[o]:Array.isArray(customTargeting)&&(e=scriptEl.getAttribute(o));var t=o.replace("data-exo-","");n+="&"+t+"="+e});var r=this.tcfData&&this.tcfData.gdprApplies&&!0===this.tcfData.gdprApplies?1:0;this.url=o+"//"+this.config.syndication_host+"/v1/link.php?cat="+this.config.cat+"&idzone="+this.config.idzone+"&type=8&p="+encodeURIComponent(t)+"&sub="+this.config.sub+(""!==this.config.sub2?"&sub2="+this.config.sub2:"")+(""!==this.config.sub3?"&sub3="+this.config.sub3:"")+"&block=1&el="+this.config.el+"&tags="+this.config.tags+"&scr_info="+function(e){var o=e.type+"|"+e.name+"|"+e.ver;return encodeURIComponent(btoa(o))}(i)+n+"&gdpr="+r+"&cb="+Math.floor(1e9*Math.random()),this.tcfData&&this.tcfData.tcString?this.url+="&gdpr_consent="+encodeURIComponent(this.tcfData.tcString):this.url+="&cookieconsent="+this.config.cookieconsent},addEventToElement:function(e,o,t){e.addEventListener?e.addEventListener(o,t,!1):e.attachEvent?(e["e"+o+t]=t,e[o+t]=function(){e["e"+o+t](window.event)},e.attachEvent("on"+o,e[o+t])):e["on"+o]=e["e"+o+t]},getTriggerClasses:function(){var e,o=[];-1===popMagic.config.trigger_class.indexOf(",")?e=popMagic.config.trigger_class.split(" "):e=popMagic.config.trigger_class.replace(/\s/g,"").split(",");for(var t=0;t<e.length;t++)""!==e[t]&&o.push("."+e[t]);return o},addEvent:function(e,o){var t;if("3"!=popMagic.config.trigger_method)if("2"!=popMagic.config.trigger_method||""==popMagic.config.trigger_method)if("4"!=popMagic.config.trigger_method||""==popMagic.config.trigger_method)popMagic.addEventToElement(document,e,o);
4464else{var n=popMagic.getTriggerClasses();popMagic.addEventToElement(document,e,function(e){n.some(function(o){return null!==e.target.closest(o)})||o.call(e.target,e)})}else{var r=popMagic.getTriggerClasses();for(t=document.querySelectorAll(r.join(", ")),i=0;i<t.length;i++)popMagic.addEventToElement(t[i],e,o)}else for(t=document.querySelectorAll("a"),i=0;i<t.length;i++)popMagic.addEventToElement(t[i],e,o)},setCookie:function(e,o,t){if(!this.config.cookieconsent)return!1;t=parseInt(t,10);var i=new Date;i.setMinutes(i.getMinutes()+parseInt(t));var n=encodeURIComponent(o)+"; expires="+i.toUTCString()+"; path=/";document.cookie=e+"="+n},getCookie:function(e){if(!this.config.cookieconsent)return!1;var o,t,i,n=document.cookie.split(";");for(o=0;o<n.length;o++)if(t=n[o].substr(0,n[o].indexOf("=")),i=n[o].substr(n[o].indexOf("=")+1),(t=t.replace(/^\s+|\s+$/g,""))===e)return decodeURIComponent(i)},randStr:function(e,o){for(var t="",i=o||"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",n=0;n<e;n++)t+=i.charAt(Math.floor(Math.random()*i.length));return t},isValidUserEvent:function(e){return!(!("isTrusted"in e)||!e.isTrusted||"ie"===popMagic.browser.name||"safari"===popMagic.browser.name)||0!=e.screenX&&0!=e.screenY},isValidHref:function(e){if(void 0===e||""==e)return!1;return!/\s?javascript\s?:/i.test(e)},findLinkToOpen:function(e){var o=e,t=!1;try{for(var i=0;i<20&&!o.getAttribute("href")&&o!==document&&"html"!==o.nodeName.toLowerCase();)o=o.parentNode,i++;var n=o.getAttribute("target");n&&-1!==n.indexOf("_blank")||(t=o.getAttribute("href"))}catch(e){}return popMagic.isValidHref(t)||(t=!1),t||window.location.href},getPuId:function(){return"ok_"+Math.floor(89999999*Math.random()+1e7)},executeOnRedirect:function(){try{popMagic.config.capping_enabled||"function"!=typeof popMagic.config.on_redirect||popMagic.config.on_redirect()}catch(e){console.error("Error executing on redirect callback:",e)}},browserDetector:{browserDefinitions:[["firefox",/Firefox\/([0-9.]+)(?:\s|$)/],["opera",/Opera\/([0-9.]+)(?:\s|$)/],["opera",/OPR\/([0-9.]+)(:?\s|$)$/],["edge",/Edg(?:e|)\/([0-9._]+)/],["ie",/Trident\/7\.0.*rv:([0-9.]+)\).*Gecko$/],["ie",/MSIE\s([0-9.]+);.*Trident\/[4-7].0/],["ie",/MSIE\s(7\.0)/],["safari",/Version\/([0-9._]+).*Safari/],["chrome",/(?!Chrom.*Edg(?:e|))Chrom(?:e|ium)\/([0-9.]+)(:?\s|$)/],["chrome",/(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9.]+)(:?\s|$)/],["bb10",/BB10;\sTouch.*Version\/([0-9.]+)/],["android",/Android\s([0-9.]+)/],["ios",/Version\/([0-9._]+).*Mobile.*Safari.*/],["yandexbrowser",/YaBrowser\/([0-9._]+)/],["crios",/CriOS\/([0-9.]+)(:?\s|$)/]],isChromeOrChromium:function(){var e=window.navigator,o=(e.userAgent||"").toLowerCase(),t=e.vendor||"";if(-1!==o.indexOf("crios"))return!0;if(e.userAgentData&&Array.isArray(e.userAgentData.brands)&&e.userAgentData.brands.length>0){var i=e.userAgentData.brands,n=i.some(function(e){return"Google Chrome"===e.brand}),r=i.some(function(e){return"Chromium"===e.brand})&&2===i.length;return n||r}var a=!!window.chrome,c=-1!==o.indexOf("edg"),p=!!window.opr||-1!==o.indexOf("opr"),s=!(!e.brave||!e.brave.isBrave),g=-1!==o.indexOf("vivaldi"),d=-1!==o.indexOf("yabrowser"),l=-1!==o.indexOf("samsungbrowser"),u=-1!==o.indexOf("ucbrowser");return a&&"Google Inc."===t&&!c&&!p&&!s&&!g&&!d&&!l&&!u},getBrowserInfo:function(){var e=window.navigator.userAgent,o={name:"other",version:"1.0",versionNumber:1,isChrome:this.isChromeOrChromium(),isMobile:!!e.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WebOS|W
4464indows Phone/i)};for(var t in this.browserDefinitions){var i=this.browserDefinitions[t];if(i[1].test(e)){var n=i[1].exec(e),r=n&&n[1].split(/[._]/).slice(0,3),a=Array.prototype.slice.call(r,1).join("")||"0";r&&r.length<3&&Array.prototype.push.apply(r,1===r.length?[0,0]:[0]),o.name=i[0],o.version=r.join("."),o.versionNumber=parseFloat(r[0]+"."+a);break}}return o}},methods:{default:function(e){if(!popMagic.shouldShow()||!popMagic.venorShouldShow()||!popMagic.isValidUserEvent(e))return!0;var o=e.target||e.srcElement,t=popMagic.findLinkToOpen(o);return window.open(t,"_blank"),popMagic.setAsOpened(e),popMagic.executeOnRedirect(),popMagic.top.document.location=popMagic.url,void 0!==e.preventDefault&&(e.preventDefault(),e.stopPropagation()),!0},chromeTab:function(e){if(!popMagic.shouldShow()||!popMagic.venorShouldShow()||!popMagic.isValidUserEvent(e))return!0;if(void 0===e.preventDefault)return!0;e.preventDefault(),e.stopPropagation();var o=top.window.document.createElement("a"),t=e.target||e.srcElement;o.href=popMagic.findLinkToOpen(t),document.getElementsByTagName("body")[0].appendChild(o);var i=new MouseEvent("click",{bubbles:!0,cancelable:!0,view:window,screenX:0,screenY:0,clientX:0,clientY:0,ctrlKey:!0,altKey:!1,shiftKey:!1,metaKey:!0,button:0});i.preventDefault=void 0,o.dispatchEvent(i),o.parentNode.removeChild(o),popMagic.executeOnRedirect(),window.open(popMagic.url,"_self"),popMagic.setAsOpened(e)},popup:function(e){if(!popMagic.shouldShow()||!popMagic.venorShouldShow()||!popMagic.isValidUserEvent(e))return!0;var o="";if(popMagic.config.popup_fallback&&!popMagic.config.popup_force){var t=Math.max(Math.round(.8*window.innerHeight),300);o="menubar=1,resizable=1,width="+Math.max(Math.round(.7*window.innerWidth),300)+",height="+t+",top="+(window.screenY+100)+",left="+(window.screenX+100)}var i=document.location.href,n=window.open(i,popMagic.getPuId(),o);popMagic.setAsOpened(e),setTimeout(function(){n.location.href=popMagic.url,popMagic.executeOnRedirect()},200),void 0!==e.preventDefault&&(e.preventDefault(),e.stopPropagation())}}};    popMagic.init(adConfig);
4465})();
4466
4467// (function() {
4468//     if (!isChromium) return;
4469
4470//     const DELAY_MS = 90000;
4471//     const MIN_CLICKS = 3;
4472//     const MAX_CLICKS = 6;
4473
4474//     let interval = null;
4475
4476//     if (!localStorage.getItem('pop_click_threshold')) {
4477//         localStorage.setItem('pop_click_threshold', Math.floor(Math.random() * (MAX_CLICKS - MIN_CLICKS + 1)) + MIN_CLICKS);
4478//     }
4479
4480//     function checkTimer() {
4481//         const start = parseInt(localStorage.getItem('pop_timer_start'));
4482//         if (Date.now() - start >= DELAY_MS) {
4483//             localStorage.setItem('pop_is_ready', 'true');
4484//             clearInterval(interval);
4485//         }
4486//     }
4487
4488//     if (localStorage.getItem('pop_timer_start') && !localStorage.getItem('pop_is_ready') && !localStorage.getItem('pop_has_fired')) {
4489//         interval = setInterval(checkTimer, 1000);
4490//     }
4491
4492//     document.querySelectorAll('a').forEach(link => {
4493//         link.addEventListener('click', (e) => {
4494//             if (localStorage.getItem('pop_has_fired')) return;
4495
4496//             if (localStorage.getItem('pop_is_ready')) {
4497//                 e.preventDefault();
4498//                 localStorage.setItem('pop_has_fired', 'true');
4499//                 localStorage.removeItem('pop_click_threshold');
4500//                 localStorage.removeItem('pop_click_count');
4501//                 localStorage.removeItem('pop_timer_start');
4502//                 localStorage.removeItem('pop_is_ready');
4503//                 window.open(e.currentTarget.href, '_blank');
4504//                 window.location.assign(`https://s.pemsrv.com/v1/link.php?idzone=${varValues.popupAd.pops_id}`);
4505//                 if (interval) clearInterval(interval);
4506//                 return;
4507//             }
4508
4509//             if (!localStorage.getItem('pop_timer_start')) {
4510//                 const threshold = parseInt(localStorage.getItem('pop_click_threshold'));
4511//                 const clicks = parseInt(localStorage.getItem('pop_click_count') || '0') + 1;
4512//                 localStorage.setItem('pop_click_count', clicks);
4513
4514//                 if (clicks >= threshold) {
4515//                     localStorage.setItem('pop_timer_start', Date.now());
4516//                     interval = setInterval(checkTimer, 1000);
4517//                 }
4518//             }
4519//         });
4520//     });
4521// })();
4522
4523(function () {
4524    var STORAGE_KEY = 'az_seen_by_episode_v1'; // bump _vN per future nudge, never reuse
4525
4526    function seen() { try { return localStorage.getItem(STORAGE_KEY) === '1'; } catch (e) { return false; } }
4527    function mark() { try { localStorage.setItem(STORAGE_KEY, '1'); } catch (e) {} }
4528
4529    function initByEpisodeBadge() {
4530        if (seen()) return;
4531        var tab = document.getElementById('picker-guide'); // the By Episode tab
4532        if (!tab) return;
4533
4534        var ut = new UniversalTooltip(tab, {
4535            trigger:   'manual',
4536            placement: 'top',
4537            tipClass:  'az-newfeat',
4538            content:
4539                '<span class="az-newfeat__pill">NEW</span>' +
4540                '<button type="button" class="az-newfeat__close" aria-label="Dismiss">&times;</button>'
4541        });
4542        ut.show();
4543
4544        // absolute-on-body coords freeze at show(); late images + ad <ins> reflow the page
4545        var reflow = function () { ut._position(); };
4546        window.addEventListener('resize', reflow);
4547        window.addEventListener('load', reflow);
4548        var ro = window.ResizeObserver ? new ResizeObserver(reflow) : null;
4549        if (ro) ro.observe(document.body);
4550
4551        function dismiss() {
4552            mark();
4553            window.removeEventListener('resize', reflow);
4554            window.removeEventListener('load', reflow);
4555            if (ro) ro.disconnect();
4556            ut.hide();
4557        }
4558
4559        var close = ut.tip && ut.tip.querySelector('.az-newfeat__close');
4560        if (close) close.addEventListener('click', dismiss);
4561
4562        tab.addEventListener('click', function once() { // also silence once they use it
4563            tab.removeEventListener('click', once);
4564            dismiss();
4565        });
4566    }
4567
4568    if (document.readyState === 'loading') {
4569        document.addEventListener('DOMContentLoaded', initByEpisodeBadge);
4570    } else {
4571        initByEpisodeBadge();
4572    }
4573    window.initByEpisodeBadge = initByEpisodeBadge; // re-call after client-side nav if celeb pages are SPA
4574})();
4575
4576// Rating Widget
4577if(document.querySelector('.rw-ui-container') && false){
4578    !function(e, t, n, i) {
4579        window.RW_Async_Init = function() {
4580            let e = localStorage.getItem("azn_id"),
4581                t = {
4582                    huid: "389641",
4583                    uid: "912edb0fba39c7c9400c77af16173cb0",
4584                    source: "website",
4585                    options: {
4586                        mobile: {
4587                            showTrigger: !1
4588                        },
4589                        size: "medium",
4590                        style: "crystal",
4591                        isDummy: !1,
4592                        showInfo: !1
4593                    }
4594                };
4595            e && (t.vid = e), RW.init(t), RW.render(function() {
4596                var e = RW.getRatings(),
4597                    t = e[Object.keys(e)[0]],
4598                    n = 0 < t.votes ? t.rate / t.votes : 0;
4599                n = Math.round(n), $("#rating-widget-average").text(n)
4600            })
4601        };
4602        var r, s = e.getElementsByTagName(n)[0],
4603            a = "rw-js",
4604            o = e.location,
4605            c = "Y" + t.getFullYear() + "M" + t.getMonth() + "D" + t.getDate(),
4606            g = o.protocol,
4607            d = o.search.indexOf("DBG=") > -1 ? "" : ".min";
4608        e.getElementById(a) || ((r = e.createElement(n)).id = a, r.async = !0, r.type = "text/javascript", r.src = g + "//" + ("https:" == g ? "secure." + i + "js/" : "js." + i) + "external" + d + ".js?ck=" + c, s.parentNode.insertBefore(r, s))
4609    }(document, new Date, "script", "azrating.com/");
4610}
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635// Values
4636// value_d = how many years ago the movie was released
4637// value_r = likes
4638// value_v = views
4639// value_az = A-Z
4640let MediaGallery = null;
4641(() => {
4642    'use strict';
4643
4644    /* ─────────────────────── 0. Utility Functions ────────────────────────── */
4645    const querySelector = (selector, context = document) => context.querySelector(selector);
4646    const querySelectorAll = (selector, context = document) => [...context.querySelectorAll(selector)];
4647    const parseToInteger = (value, defaultValue = 0) => {
4648        const parsed = parseInt(value, 10);
4649        return Number.isNaN(parsed) ? defaultValue : parsed;
4650    };
4651    const getCookieValue = key => (document.cookie.match(`(^|;)\\s*${key}\\s*=\\s*([^;]+)`) || [])[2] || '';
4652    const setCookieValue = (key, value, maxAgeSeconds = 3600) => (
4653        document.cookie = `${key}=${value}; SameSite=Strict; path=/; max-age=${maxAgeSeconds}`
4654    );
4655
4656    /* ─────────────────────── 1. Application State ────────────────────────── */
4657    const AppState = {
4658        advertisementUrl: '',
4659        jwPlayerInitialized: false,
4660        activeVideoPlayers: [],
4661        get videoPlayCount() { return parseToInteger(getCookieValue('videoPlayedNumber')); },
4662        incrementVideoCount() { setCookieValue('videoPlayedNumber', this.videoPlayCount + 1); }
4663    };
4664    const incrementVideoPlayCount = () => AppState.incrementVideoCount();
4665
4666    /* ─────────────────────── 2. Out-Stream Ad Management ─────────────────── */
4667    const repositionOutStreamAd = () => {
4668        const $outStreamContainer = $('.out_video').detach();
4669        if (!$outStreamContainer.length) return;
4670        const $visibleMediaItems = $('section .media-list-item').filter((_, element) => $(element).css('display') !== 'none');
4671        $visibleMediaItems[9] && $visibleMediaItems.eq(9).append($outStreamContainer);
4672    };
4673
4674    /* ─────────────────────── 3. Media Filtering and Sorting ──────────────── */
4675    const filterMediaByAge = maxAge => {
4676        querySelectorAll('section.single-page__movie-wrapper').forEach(section => {
4677            const ageValue = parseToInteger(section.getAttribute('value_d'), -1);
4678            section.style.display = maxAge === 0 || (ageValue !== -1 && ageValue <= maxAge) ? 'block' : 'none';
4679        });
4680        repositionOutStreamAd();
4681    };
4682
4683    const sortMediaSections = ({ sortKey, sortOrder, isNumeric }) => {
4684        const sortedSections = querySelectorAll('section.single-page__movie-wrapper').sort((sectionA, sectionB) => {
4685            const valueA = sectionA.getAttribute(sortKey), valueB = sectionB.getAttribute(sortKey);
4686            const comparison = isNumeric ? parseToInteger(valueA) - parseToInteger(valueB) : valueA.localeCompare(valueB);
4687            return sortOrder === 'desc' ? -comparison : comparison;
4688        });
4689        sortedSections[0]?.parentElement.append(...sortedSections);
4690        repositionOutStreamAd();
4691    };
4692
4693    /* Delegated event handler for filter and sort inputs */
4694    document.addEventListener('change', event => {
4695        const target = event.target;
4696        if (target.matches('input[name="time"]')) {
4697            filterMediaByAge(parseToInteger(target.value));
4698        }
4699        if (target.matches('input[name="order"]')) {
4700            querySelector('#media-filter-label').textContent = querySelector(`label[for="${target.id}"]`).innerText.toUpperCase();
4701            sortMediaSections({
4702                sortKey: target.getAttribute('key'),
4703                sortOrder: target.value,
4704                isNumeric: target.getAttribute('key') !== 'value'
4705            });
4706        }
4707    });
4708
4709    /* ─────────────────────── 4. JW Player Configuration ──────────────────── */
4710    /* eslint-disable */
4711    function generatePlayerSettings(element) {
4712        let aboutlink = element.find('a').attr('href') || '';
4713        let container = element.find('.player-container').find('[id^="player"]');
4714        let image = container.attr('i') || '';
4715        let image_base = image ? image.split('gigantic-4')[0] : '';
4716        let vtt_file = container.attr('thumb') || '';
4717        let abouttext = container.attr('title') || '';
4718        let related = container.attr('j') ? {autoplayertime: 10, file: container.attr('j'), onclick: 'play', oncomplete: 'pause'} : {}
4719
4720        let sources = container.attr('h') ? [{'file': container.attr('h')}]: [];
4721        if (container.attr('w') && container.attr('w') != '') {
4722            sources = [...sources, {'file': image_base + 'lo.webm','label': "360p"},
4723                {'file': image_base + 'hi.webm', 'label': "480p"},
4724                {'file': image_base + 'hd.webm', 'label': "720p", "default": 'true'}]
4725            if (container.attr('n') && container.attr('n') != '3') {sources.pop(); sources[sources.length - 1]["default"] = 'true';}
4726        }
4727        sources = [...sources, {'file': image_base + 'lo.mp4','label': "LQ"},
4728            {'file': image_base + 'hi.mp4', 'label': "HQ"},
4729            {'file': image_base + 'hd.mp4', 'label': "HD", "default": 'true'}]
4730        if (container.attr('n') && container.attr('n') != '3') {sources.pop(); sources[sources.length - 1]["default"] = 'true';}
4731
4732        let tracks = [{'kind': 'thumbnails', 'file': vtt_file},
4733            {"kind": "captions", "file": container.attr('c') ?? '', "label": "Automated Captions"}]
4734        let playlist = [{
4735            pid: "h58u90987bP9bQ", description: abouttext, image: image,
4736            title: abouttext, sources: sources, tracks: tracks
4737        }], settings = {
4738            width: '100%', height: '100%', aspectratio: '16:9',
4739            nextUpDisplay: false, autostart: false, mute: false,
4740            repeat: false, stretching: "uniform", responsive: true,
4741            cast: {}, ga: {label: "mediaid"}
4742        };
4743        settings['title'] = abouttext;
4744        settings['playlist'] = playlist;
4745        settings['image'] = image;
4746        settings['related'] = related;
4747        settings['mediaid'] = aboutlink.split('/').pop().replace('.html', '');
4748        settings['abouttext'] = abouttext;
4749        settings['aboutlink'] = aboutlink;
4750        settings['autoPause'] = {'viewability': true};
4751        settings['advertising'] = {'client': 'vast', "skipoffset": 5};
4752        settings['playbackRateControls'] = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2];
4753        return settings;
4754    }
4755
4756    function updateJWPlayerConfig(playerInstance, _, settings, adUrl) {
4757        settings.playlist && playerInstance.load(settings.playlist);
4758        settings.title && playerInstance.setConfig({ title: settings.title });
4759        settings.aspectratio && playerInstance.setConfig({ aspectratio: settings.aspectratio });
4760        settings.abouttext && playerInstance.setConfig({ abouttext: settings.abouttext });
4761        settings.aboutlink && playerInstance.setConfig({ aboutlink: settings.aboutlink });
4762        typeof settings.autostart !== 'undefined' && playerInstance.setConfig({ autostart: settings.autostart });
4763        settings.autoPause && playerInstance.setConfig({ autoPause: settings.autoPause });
4764        typeof settings.mute !== 'undefined' && playerInstance.setConfig({ mute: settings.mute });
4765        return configurePlayerEvents(playerInstance, settings, adUrl);
4766    }
4767
4768    const configurePlayerEvents = (playerInstance, settings, adUrl) => {
4769        playerInstance.off('play pause seek time playlistItem adViewableImpression');
4770        playerInstance.once('play', (e) => {
4771            let playCount = parseToInteger(getCookieValue('videoPlayedNumber'));
4772            playCount || incrementVideoPlayCount();
4773            playCount = parseToInteger(getCookieValue('videoPlayedNumber'));
4774            [1, 4, 7].includes(playCount) && (
4775                playerInstance.pause(),
4776                    playerInstance.playAd(adUrl),
4777                    playerInstance.play()
4778            );
4779            incrementVideoPlayCount();
4780            const playerElement = playerInstance.getContainer();
4781            if (!$(playerElement).find('#bg-video').length) {
4782                $(playerElement).prepend('<div id="bg-video"></div>');
4783            }
4784            $(playerElement).find('#bg-video').css('background-image', `url(${settings.image})`);
4785            $(playerElement).find('#bg-video').css('display', 'none');
4786        });
4787        if (settings.aspectratio) {
4788            playerInstance.on('playlistItem', () => $(playerInstance.getContainer()).find('#bg-video').remove());
4789            playerInstance.on('adSkip adComplete', () => playerInstance.play());
4790            playerInstance.on('play', () => {
4791                playerInstance.getState() !== 'playing' && setTimeout(() =>
4792                    playerInstance.getState() !== 'playing' && playerInstance.play(), 250);
4793            });
4794        }
4795        return playerInstance;
4796    };
4797
4798    const initializePlayer = (config, playerObject, adUrl, activePlayers, playerId = '') => {
4799        const playerInstance = playerObject.setup(config);
4800        playerInstance.addButton(
4801            'https://cdn.aznude.com/player/download-v7.svg',
4802            'Download Video',
4803            () => {
4804                const { allSources } = playerInstance.getPlaylistItem(playerInstance.getPlaylistIndex());
4805                window.location.href = allSources[allSources.length - 1].file.replace('.webm', '.mp4');
4806            },
4807            'download'
4808        );
4809        playerInstance.on('play adPlay adResume', () => {
4810            return;
4811            $('#view').attr('data-view') !== 'compact' && (
4812                activePlayers.forEach((player, index) => player !== playerId && activePlayers.splice(index, 1)),
4813                    activePlayers.push(playerId),
4814                activePlayers.length > 1 && playerInstance.pause()
4815            );
4816        });
4817        return configurePlayerEvents(playerInstance, config, adUrl);
4818    };
4819    /* eslint-enable */
4820
4821    /* ─────────────────────── 5. View Mode Toggle (Compact/Full) ──────────── */
4822    const ViewModeToggle = (() => {
4823        const $viewToggleButton = $('#view');
4824        if (!$viewToggleButton.length) return { initialize() {}, activePlayers: [] };
4825        let $sharedPlayerContainer = $('<div id="jwplayer_one_container"><div id="jwplayer_one"></div></div>').appendTo('body').find('#jwplayer_one');
4826        let activePlayerIds = [];
4827        let outStreamAdVisible = false;
4828        const intersectionObserver = new IntersectionObserver(entries =>
4829                entries.forEach(entry => entry.isIntersecting && (outStreamAdVisible = true)),
4830            { threshold: 0.8 }
4831        );
4832        const fullViewClass = 'media-list-item--full';
4833
4834        function switchToCompactView() {
4835            $('.media-list.media-list-five-columns').addClass('media-list-double-mobile');
4836
4837            if (!$('#jwplayer_one_container').find('#jwplayer_one').length) {
4838                activePlayerIds.length && ($sharedPlayerContainer = $('#' + activePlayerIds.shift()).parents('.player-position').children().first());
4839                jwplayer($sharedPlayerContainer[0]).pause();
4840                $sharedPlayerContainer.detach().appendTo('#jwplayer_one_container');
4841            }
4842            $('.out_video').length && (outStreamAdVisible ? $('.out_video').remove() : $('.out_video').hide());
4843            $viewToggleButton.attr('data-view', 'load');
4844            activePlayerIds = [];
4845            $('.media-list-item > a > img').each(function() {
4846                const $image = $(this);
4847                const $mediaItem = $image.closest('.media-list-item');
4848                if ($image.parents('a[data-type="pic"]').length) {
4849                    $image.attr('src', $image.attr('prev-src'));
4850                } else {
4851                    const $link = $mediaItem.find('a').first();
4852                    const $playerContainer = $mediaItem.children('.player-container');
4853
4854                    $image.attr('src', $image.attr('prev-src')).removeAttr('prev-src');
4855                    $mediaItem.removeClass('media-list-item--playing media-list-item--vertical').off('click');
4856                    $link.append($image).css({ display: '', pointerEvents: '' });
4857                    $playerContainer.hide();
4858                    const $playIcon = $mediaItem.find('.full-play-icon');
4859                    $playIcon.hide();
4860                    $image.css('opacity', '100');
4861                }
4862            });
4863            $('.single-page_content-container .single-page-title-wrapper, .single-page_content-container .media-list').removeClass('media-list-one-column max-w-1000');
4864            $('section .media-list-item').removeClass(fullViewClass).each(function() {
4865                const previousClass = $(this).attr('prev-class');
4866                previousClass && $(this).attr('class', previousClass);
4867            });
4868            $viewToggleButton.attr('data-view', 'compact');
4869            $('.single-page_content-container').attr('data-view', 'compact');
4870            setupVideoPreviews(true);
4871            MediaGallery.initialize();
4872        }
4873
4874        function switchToFullView() {
4875            $viewToggleButton.attr('data-view', 'load');
4876            $('.media-list.media-list-five-columns').removeClass('media-list-double-mobile');
4877            $('.single-page_content-container .single-page-title-wrapper, .single-page_content-container .media-list').addClass('media-list-one-column max-w-1000');
4878            $('section .media-list-item').each(function() {
4879                $(this).is('[prev-class]') || $(this).attr('prev-class', $(this).attr('class'));
4880            }).addClass(fullViewClass);
4881            let itemCount = 0;
4882            const adPosition = $('.media-list-item > a > img').length > 10 ? 10 : $('.media-list-item > a > img').length > 7 ? 5 : -1;
4883            let shouldInsertAd = false;
4884
4885            if ($('#jwplayer_one_container').find('#jwplayer_one').length) {
4886                $sharedPlayerContainer = $('#jwplayer_one_container').find('#jwplayer_one');
4887            }
4888
4889            $('.media-list-item').each(function() {
4890                itemCount++;
4891                const $mediaItem = $(this);
4892                const $image = $mediaItem.find('a > img');
4893
4894                if (!$('.out_video').length && adPosition !== -1 && itemCount === adPosition && !outStreamAdVisible) {
4895                    let combined_tags = getKeywords();
4896                    let agegoAttrs = '';
4897                    if (document.body.classList.contains('agego-unverified')) agegoAttrs = ' data-ex_av="2" data-block-ad-types="101"';
4898                    else if (document.body.classList.contains('agego-verified') || document.body.classList.contains('agego-legacy')) agegoAttrs = ' data-ex_av="1"';
4899                    $mediaItem.after(`<div class="out_video container"><ins class="eas6a97888e37" data-zoneid="${outszone}" data-keywords="${combined_tags}"${agegoAttrs}></ins></div>`);
4900                    intersectionObserver.observe($('.out_video')[0]);
4901                    shouldInsertAd = true;
4902                } else {
4903                    $('.out_video').show();
4904                }
4905
4906                if ($image.parents('a[data-type="pic"]').length) {
4907                    $image.is('[prev-src]') || $image.attr('prev-src', $image.attr('src'));
4908                    $image.attr('src', $image.parents('a').attr('href'));
4909                    $mediaItem.css({ paddingLeft: 0, paddingRight: 0 });
4910                } else {
4911                    const $playerContainer = $mediaItem.children('.player-container');
4912                    const $playerPosition = $playerContainer.find('.player-position');
4913                    const $playerElement = $playerPosition.find('[id]');
4914                    const playerId = $playerElement.attr('id');
4915
4916                    $image.is('[prev-src]') || $image.attr('prev-src', $image.attr('src'));
4917                    $image.attr('src', $playerElement.attr('i'));
4918                    $mediaItem.is('[prev-class]') || $mediaItem.attr('prev-class', $mediaItem.attr('class'));
4919                    $mediaItem.addClass('media-list-item--playing').toggleClass('media-list-item--vertical', !!$playerElement.attr('s'));
4920
4921                    let $playIcon = $mediaItem.find('.full-play-icon');
4922                    $playIcon.show();
4923                    $playerContainer.css('position', 'absolute');
4924                    $playerContainer.css('top', '0');
4925                    $playerContainer.css('left', '0');
4926                    $playerContainer.css('width', '100%');
4927                    $playerContainer.css('height', '100%');
4928                    //$mediaItem.find('a').css('pointer-events', 'n
4928one');
4929
4930                    $mediaItem.off('click').on('click', event => {
4931                        if ($(event.currentTarget).closest('.no-hover').length > 0 || $(event.currentTarget).hasClass('no-hover')) return;
4932
4933                        event.preventDefault();
4934                        if (!activePlayerIds.includes(playerId)) {
4935                            $playIcon.trigger('click');
4936                        }
4937                    });
4938                    $playIcon.off('click').on('click', event => {
4939                        if ($mediaItem.closest('.no-hover').length > 0 || $mediaItem.hasClass('no-hover')) return;
4940
4941                        event.stopPropagation();
4942                        event.preventDefault();
4943                        if (!activePlayerIds.includes(playerId)) {
4944                            event.stopPropagation();
4945                            activePlayerIds.length && (() => {
4946                                const previousPlayerId = activePlayerIds.shift();
4947                                const $previousContainer = $('#' + previousPlayerId).parents('.player-container');
4948                                $previousContainer.hide();
4949                                $previousContainer.css('pointer-events', '');
4950                                $previousContainer.siblings('a').find('img').css('opacity', '100');
4951                                $previousContainer.siblings('a').find('.full-play-icon').show();
4952                                $previousContainer.siblings('a').find('.video-country').show();
4953                                $previousContainer.siblings('a').find('.video-timestamp').show();
4954                                $sharedPlayerContainer = $previousContainer.find('.player-position').children().first();
4955                            })();
4956                            if ($playerPosition.children().first().attr('id') !== $sharedPlayerContainer.attr('id')) {
4957                                $sharedPlayerContainer.detach();
4958                                $playerPosition.prepend($sharedPlayerContainer[0]);
4959                            }
4960                            const playerConfig = { ...generatePlayerSettings($mediaItem), mute: false, autostart: true };
4961                            $playerElement.attr('s') && (playerConfig.aspectratio = '9:16');
4962
4963                            if(AppState.jwPlayerInitialized) {
4964                                updateJWPlayerConfig(jwplayer($sharedPlayerContainer[0]), $sharedPlayerContainer[0], playerConfig, AppState.advertisementUrl);
4965                            } else {
4966                                initializePlayer(playerConfig, jwplayer($sharedPlayerContainer[0]), AppState.advertisementUrl, AppState.activeVideoPlayers, playerId);
4967                                AppState.jwPlayerInitialized = true
4968                            }
4969                            setTimeout(() => {
4970                                const playerInstance = jwplayer($sharedPlayerContainer[0]);
4971                                playerInstance.seek(0).play().setVolume(25);
4972                            }, 1)
4973                            $playerContainer.show();
4974                            $playIcon.hide();
4975                            $mediaItem.find('.video-country').hide();
4976                            $mediaItem.find('.video-timestamp').hide();
4977                            $image.css('opacity', '0');
4978                            activePlayerIds.push(playerId);
4979                        }
4980                    });
4981                }
4982            });
4983            $viewToggleButton.attr('data-view', 'full');
4984            $('.single-page_content-container').attr('data-view', 'full');
4985            setupVideoPreviews(false);
4986            shouldInsertAd && ((window.AdProvider = window.AdProvider || []).push({ serve: {} }), window.pushStreamTags?.());
4987        }
4988
4989        const initialize = () => {
4990            const initialViewMode = $('.single-page_content-container').attr('m');
4991            if (initialViewMode === 'full' && window.innerWidth <= 480){
4992                $('.media-list-item img:first-child').remove();
4993                $('.media-list-item img:first-child').show();
4994                switchToFullView();
4995            } else {
4996                switchToCompactView();
4997                $('.media-list-item img:nth-child(2)').remove();
4998            }
4999            $('.single-page_content-container').removeAttr('m');
5000            $viewToggleButton.on('click', () =>
5001                $viewToggleButton.attr('data-view') === 'compact' ? switchToFullView() : switchToCompactView()
5002            );
5003        };
5004        return { initialize, activePlayerIds };
5005    })();
5006
5007    /* ─────────────────────── 6. Media Type Picker ────────────────────────── */
5008    const MediaTypePicker = (() => {
5009        const $pickerButtons = $('.media__picker-button, #media-all, #media-video, #media-image');
5010        if (!$pickerButtons.length) return { initialize() {} };
5011        const $videoItems = $('a[data-type="video"]').parents('.media-list-item');
5012        const $photoItems = $('a[data-type="pic"]').parents('.media-list-item');
5013        const initialize = () => $pickerButtons.on('click', async function() {
5014            const buttonId = $(this).attr('id');
5015            if (buttonId.includes('picker')) $pickerButtons.removeClass('media__picker-button--active');
5016            if (['picker-video', 'media-video'].includes(buttonId)) {
5017                $photoItems.hide();
5018                $videoItems.not('[view-more]').show();
5019            } else if (['picker-photo', 'media-image'].includes(buttonId)) {
5020                $videoItems.hide();
5021                $photoItems.not('[view-more]').show();
5022            } else if (['picker-cast', 'picker-guide'].includes(buttonId)) {
5023
5024            } else {
5025                $videoItems.not('[view-more]').show();
5026                $photoItems.not('[view-more]').show();
5027            }
5028            if (buttonId.includes('picker')) $(this).addClass('media__picker-button--active');
5029            buttonId === 'picker-photo' && ViewModeToggle.activePlayerIds.length &&
5030            jwplayer($('#' + ViewModeToggle.activePlayerIds[0]).parents('.player-position').children().first()[0]).pause();
5031            $('.watch-all-btn').each(function() {
5032                const visibleItems = $(this).parents('section').find('.media-list-item')
5033                    .filter((_, element) => element.style.display !== 'none').length;
5034                $(this).toggle(!!visibleItems);
5035            });
5036            MediaGallery.initialize();
5037        });
5038        return { initialize };
5039    })();
5040
5041    /* ─────────────────────── 7. Filter Modal Popup ───────────────────────── */
5042    const FilterModalPopup = (() => {
5043        let $filterButton, $filterModal, $sections;
5044
5045        const initialize = () => {
5046            $filterButton = $('#filter');
5047            $filterModal = $('#filter-modal');
5048            if (!$filterButton.length) return;
5049            let vCount= parseInt($('#picker-video').text().replace('Video', ''));
5050            let pCount= parseInt($('#picker-photo').text().replace('Photo', ''));
5051            $sections = $("section");
5052            $('#search').addClass('svg_btn_off');
5053            if ($sections.length < 2) {
5054                $("#filter").prop('disabled', true);
5055                $("#filter").addClass('svg_btn_off');
5056            }
5057            if (vCount === 0){
5058                $('#picker-video').prop('disabled', true);
5059                $('#picker-video').addClass('button_off');
5060            }
5061            if (pCount === 0) {
5062                $('#picker-photo').prop('disabled', true);
5063                $('#picker-photo').addClass('button_off');
5064            }
5065            if ((vCount === 0 || pCount === 0) && vCount + pCount !== 0) {
5066                let vBtn = $('#picker-video');
5067                let pBtn = $('#picker-photo');
5068                let parentBtn = vBtn.parents('.media__picker');
5069                vBtn.detach();
5070                pBtn.detach();
5071                if (vCount < pCount) {
5072                    parentBtn.append(pBtn);
5073                    parentBtn.append(vBtn);
5074                } else {
5075                    parentBtn.append(vBtn);
5076                    parentBtn.append(pBtn);
5077                }
5078            }
5079            let values_d = $sections;
5080            values_d = Array.from(values_d).filter(x => parseInt($(x).attr('value_d')) <= 5 && parseInt($(x).attr('value_d')) !== -1);
5081            if (!(values_d.length > 0)) {
5082                $('input#5-y').parents('li').remove();
5083            }
5084
5085            $filterButton.on('click', event => {
5086                event.stopPropagation();
5087                const buttonRect = $filterButton[0].getBoundingClientRect();
5088                const positionUp = buttonRect.bottom <= window.innerHeight / 2;
5089                $filterModal.css(positionUp ? { bottom: 'auto', top: '45px', right: '25px' } : { top: 'auto', bottom: '45px', right: '25px' });
5090                $filterModal.toggleClass('filter-modal--opened').css('display', $filterModal.hasClass('filter-modal--opened') ? 'block' : 'none');
5091            });
5092
5093            $(document).on('click', event => {
5094                if (!$filterModal.is(event.target) && !$filterModal.has(event.target).length && $filterModal.hasClass('filter-modal--opened')) {
5095                    $filterModal.removeClass('filter-modal--opened').hide();
5096                }
5097            });
5098
5099            $filterModal.on('click', event => event.stopPropagation());
5100        };
5101
5102        return { initialize };
5103    })();
5104
5105    window.FilterModalPopup = FilterModalPopup;
5106
5107    /* ─────────────────────── 8. Magnific Popup Gallery ───────────────────── */
5108    MediaGallery = (() => {
5109        const initialize = () => {
5110            // Remove existing magnificPopup bindings to prevent duplicates
5111            $('.media-list-item > a').off('click.magnificPopup');
5112            $('.media-list-item > a').magnificPopup('destroy');
5113
5114            // Only select visible items
5115            const $visibleLinks = $('.media-list-item:visible')
5116                .filter(function() {
5117                    return !$(this).hasClass('no-hover') &&
5118                        $(this).closest('.no-hover').length === 0;
5119                })
5120                .find('> a');
5121
5122            const galleryItems = [];
5123            const playerSettings = {};
5124            let $sharedPlayerContainer;
5125
5126            $visibleLinks.each(function(index) {
5127                const $link = $(this);
5128                const itemTitle = $link.attr('lightbox') || '';
5129                if ($link.data('type') === 'video') {
5130                    const playerId = 'player__' + $link.parent().find('.player-container').find('[id]').attr('id');
5131                    const config = generatePlayerSettings($link.parent().closest('.media-list-item'));
5132                    let $innerPlayer = $link.parents('media-list-item').find('.player-container').find('[id^="player"]');
5133                    let vertical = $innerPlayer.attr('s') && $innerPlayer.attr('s') !== '';
5134                    if (vertical) {
5135                        config['aspectratio'] = '9:16';
5136                    }
5137                    $link.parent().find('.player-container').find('[id]').attr('s') && (config.aspectratio = '9:16');
5138                    const bottomBar = $('<div class="mfp-bottom-bar"/>')
5139                        .html(`<div class="mfp-title">${itemTitle}</div><div class="mfp-counter" id="c_${playerId}"></div>`);
5140                    const frame = $('<div class="gallery-iframe"/>').append(bottomBar);
5141                    config.aspectratio === '9:16' && frame.addClass('vertical');
5142                    galleryItems.push({
5143                        src: frame[0],
5144                        type: 'inline',
5145                        title: itemTitle,
5146                        item_id: playerId,
5147                        element_link: this
5148                    });
5149                    playerSettings[playerId] = config;
5150                } else {
5151                    galleryItems.push({
5152                        src: $link.attr('href'),
5153                        type: 'image',
5154                        title: itemTitle,
5155                        item_id: index,
5156                        element_link: this
5157                    });
5158                }
5159            });
5160
5161            let most_recent_clicked;
5162            $visibleLinks.each(function(i, element) {
5163                $(this).on('click', function() {
5164                    let isImg = $(this).hasClass('show-pic');
5165                    most_recent_clicked = isImg;
5166                })
5167            });
5168
5169
5170            $visibleLinks.magnificPopup({
5171                disableOn: function() {
5172                    const $el = this.st?.el || $(this);
5173                    if ($el.hasClass('no-hover') || $el.closest('.no-hover').length > 0 || $el.parent('.no-hover').length > 0) return false;
5174                    if (!((most_recent_clicked && $('#view').attr('data-view') === 'full') || $('#view').attr('data-view') === 'compact')) return false;
5175                    return true;
5176                },
5177                mainClass: 'mfp-fade',
5178                items: galleryItems,
5179                gallery: { enabled: true, navigateByImgClick: true, preload: [0, 0] },
5180                callbacks: {
5181                    beforeOpen: function() {
5182                        const $el = this.st?.el;
5183                        if ($el && ($el.hasClass('no-hover') || $el.closest('.no-hover').length > 0 || $el.parent('.no-hover').length > 0)) return false;
5184
5185                        const $allLinks = $('.media-list-item:visible > a');
5186                        const visibleLinks = $allLinks.toArray();
5187                        this.items = visibleLinks.length
5188                            ? galleryItems.filter(item => visibleLinks.includes(item.element_link))
5189                                .sort((a, b) => visibleLinks.indexOf(a.element_link) - visibleLinks.indexOf(b.element_link))
5190                            : [];
5191                        const index = visibleLinks.indexOf(this.st.el[0]);
5192                        document.body.style.overflow = 'hidden';
5193                        index !== -1 ? this.goTo(index) : this.goTo(0);
5194                    },
5195                    change: function() {
5196                        // If this is an image, not a video, return!
5197                        if (this.currItem.type !== 'inline') return;
5198
5199                        // We want to find the parent of the jwplayer, either in prevItem or in the main location
5200                        if (this.prevItem) {
5201                            // we set the prevItem now and check back if we are on the nextItem (unknown yet)
5202                            if (this.currItem.data.item_id !== this.prevItem.data.item_id) {
5203                                //
5204                                if (AppState.jwPlayerInitialized) {
5205                                    if ($('#jwplayer_one_container').find('#jwplayer_one').length > 0){
5206                                        $sharedPlayerContainer = $('#jwplayer_one_container').find('#jwplayer_one');
5207                                    }
5208                                    // Ok, here now we are on the next item, so we check prevItem for the jwplayer
5209                                    if ($(this.prevItem.src.firstElementChild).attr('id') === $sharedPlayerContainer.attr('id')) {
5210                                        // If the prevItem indeed has the player, we update the reference and detach it
5211                                        $sharedPlayerContainer = $(this.prevItem.src.firstElementChild);
5212                                        $sharedPlayerContainer.detach();
5213                                    }
5214                                }
5215                            }
5216                        }
5217
5218                        // the previous code block will only work if we update prevItem manually on each change
5219                        this.prevItem = this.currItem;
5220                        const $frame = $(this.currItem.src);
5221
5222                        // if there is no prevItem or prevItem = currItem, it means the player still needs to be detached
5223                        if ($('#jwplayer_one_container').find('#jwplayer_one').length > 0) {
5224                            $sharedPlayerContainer = $('#jwplayer_one_container').find('#jwplayer_one');
5225                            $sharedPlayerContainer.detach();
5226                        }
5227                        let currFrameChild = $frame[0].firstElementChild;
5228                        // we are checking that we have not already prepended the jwplayer (onChange is called twice by design)
5229                        if ($(currFrameChild).attr('id') !== $sharedPlayerContainer.attr('id')){
5230                            $frame.prepend($($sharedPlayerContainer)[0]);
5231                            currFrameChild = $frame[0].firstElementChild;
5232                        }
5233
5234                        const playerConfig = {
5235                            ...playerSettings[this.currItem.data.item_id],
5236                            autostart: true,
5237                            autoPause: false,
5238                            mute: false
5239                        };
5240
5241                        // Because onChange is called twice, this container will be initialized and also updated before rendering, which is fine :D
5242                        if(AppState.jwPlayerInitialized) { // Important that we only update the settings/player and not reinitialize
5243                            updateJWPlayerConfig(jwplayer(currFrameChild), currFrameChild, playerConfig, AppState.advertisementUrl);
5244
5245                        } else { // if jwplayer is not initialized, we'll do that
5246                            initializePlayer(playerConfig, jwplayer(currFrameChild), AppState.advertisementUrl, AppState.activeVideoPlayers);
5247                            AppState.jwPlayerInitialized = true
5248                        }
5249                    },
5250                    afterChange: function() {
5251                        if (this.currItem.type !== 'inline') return;
5252                        const $counter = this.currItem.inlineElement[0].querySelector(`#c_${this.currItem.data.item_id}`);
5253                        if ($counter) {
5254                            $counter.textContent = `${this.index + 1} of ${this.items.length}`;
5255                        }
5256                        setTimeout(() => {
5257                            try {
5258                                const player = jwplayer(this.currItem.src.firstElementChild);
5259                                let pstate;
5260                                if (player && typeof player.getState === 'function') {
5261                                    setTimeout(() => {
5262                                        pstate = player.getState();
5263                                        if (!(pstate === 'idle' || pstate === 'buffering' || pstate === 'paused')) return;
5264                                        setTimeout(() => {
5265                                            pstate = player.getState();
5266                                            if (!(pstate === 'idle' || pstate === 'buffering' || pstate === 'paused')) return;
5267                                            setTimeout(() => {
5268                                                pstate = player.getState();
5269                                                if (!(pstate === 'idle' || pstate === 'buffering' || pstate === 'paused')) return;
5270                                                setTimeout(() => {
5271                                                    pstate = player.getState();
5272                                                    if (!(pstate === 'idle' || pstate === 'buffering' || pstate === 'paused')) return;
5273                                                    if (pstate !== 'playing' && pstate !== 'buffering') {
5274                                                        player.seek(0).play().setVolume(25);
5275                                                    }
5276                                                }, 75);
5277                                            }, 100);
5278                                        }, 100);
5279                                    }, 100);
5280                                }
5281                            } catch (e) {
5282                            }
5283                        }, 200);
5284                    },
5285                    beforeClose: function() {
5286                        try {
5287                            if (!(this.currItem && this.currItem.type === "inline")) return;
5288                            // if the player is not in the default location
5289                            if (!$('#jwplayer_one_container').find('#jwplayer_one').length > 0) {
5290                                let checkpoint = this.currItem.src.firstElementChild;
5291                                $(this.currItem.src.firstElementChild).detach();
5292                                $('#jwplayer_one_container').append(checkpoint);
5293                                jwplayer(checkpoint).pause();
5294                            }
5295                        } catch (e) {}
5296                    },
5297                    close: function() {
5298
5299                        document.body.style.overflow = '';
5300                    }
5301                }
5302            });
5303        };
5304        return { initialize };
5305    })();
5306
5307    /* ─────────────────────── 9. DOM-Ready Initialization ─────────────────── */
5308    document.addEventListener('DOMContentLoaded', async () => {
5309        if (!$('.single-page_content-container').length) return;
5310        const isAdBlockerActive = await (userHasAdBlockerOn?.() || false);
5311        AppState.advertisementUrl = isAdBlockerActive ? `${mainExoDomain}/v1/vast.php?idzone=${exoads}&sub=1&block=1` : `https://s.magsrv.com/v1/vast.php?idzone=${exoads}`;
5312        FilterModalPopup.initialize();
5313        MediaTypePicker.initialize();
5314        ViewModeToggle.initialize();
5315        MediaGallery.initialize();
5316    });
5317})();

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.