PageSourceSearch

https://jump.video/assets/js/main.js?v=202608131907

js jump.video collected 2026-10-08 09:01:39 UTC 43,387 bytes, 1,059 lines download raw bytes

1function createDataLayerEvent(event) {
2    let eventData;
3    switch (event) {
4        case 'pageView':
5            eventData = {
6                'event':            'Page View',
7                'userID':           window.userID,
8                'domainID':         window.domainID,
9                'propertyID':       window.propertyID,
10            };
11            window.dataLayer.push(eventData);
12            break;
13
14        case 'login':
15            if ($.cookie('event_login')) return;
16            eventData = {
17                'event':            'User Login',
18                'userID':           window.userID,
19                'domainID':         window.domainID,
20                'propertyID':       window.propertyID,
21            };
22            window.dataLayer.push(eventData);
23            $.cookie('event_login', 'true', { expires: 1, path: '/' });
24            break;
25
26        case 'transactionCompleted':
27            let pricePlanID = window.pricePlanID;
28            if ($.cookie('event_transactionCompleted_' + pricePlanID)) return;
29
30            const getParam = (param) => new URLSearchParams(window.location.search).get(param);
31
32            /* Retrieve the transaction */
33            retrieveTransaction(window.userID, pricePlanID)
34                .then((transaction) => {
35                    eventData = {
36                        'event':                    'Transaction Completed',
37                        'userID':                   window.userID,
38                        'domainID':                 window.domainID,
39                        'propertyID':               window.propertyID,
40                        'currentAsset':             getParam('asset'),
41                        'pricePlanID':              pricePlanID,
42                        'currency':                 window.currency,
43                        'transactionID':            transaction.payload.transactionID,
44                        'transactionType':          window.transactionType,
45                        'transactionValue':         (transaction.payload.price / 100).toFixed(2),
46                        'transactionAmountCharged': (transaction.payload.amountCharged / 100).toFixed(2),
47                        'discountPercentage':       transaction.payload.discountPercentage,
48                        'discountCode':             transaction.payload.discountName
49                    };
50
51                    console.log('Data layer event sent!');
52                    console.log(eventData);
53
54                    window.dataLayer.push(eventData);
55                    $.cookie('event_transactionCompleted_' + pricePlanID, 'true', { expires: 1, path: '/' });
56                })
57                .catch((error) => {
58                    console.error('Error retrieving transaction:', error);
59                });
60            break;
61
62        case 'watchedAsset':
63            let assetID = window.assetID;
64            if ($.cookie('event_watchedAsset_' + assetID)) return;
65            eventData = {
66                'event':            'Watched asset',
67                'userID':           window.userID,
68                'domainID':         window.domainID,
69                'assetID':          assetID,
70                'channelID':        window.channelID,
71                'mediaID':          window.mediaID,
72                'tags':             window.tags
73            };
74            window.dataLayer.push(eventData);
75            $.cookie('event_watchedAsset_' + assetID, 'true', { expires: 1, path: '/' });
76            break;
77    }
78}
79
80function retrieveTransaction(userID, pricePlanID, attempt = 0) {
81    return new Promise((resolve, reject) => {
82        $.ajax({
83            url: '/api/get-transaction',
84            type: 'GET',
85            data: {
86                userID: userID,
87                pricePlanID: pricePlanID
88            },
89            success: function(response) {
90                if (response.success) {
91                    resolve(response);
92                } else {
93                    if (attempt < 15) {
94                        console.log('No transaction found, retrying in 1.5 seconds');
95                        setTimeout(() => {
96                            retrieveTransaction(userID, pricePlanID, attempt + 1).then(resolve).cat
96ch(reject);
97                        }, 1500);
98                    } else {
99                        reject('Max retry attempts reached');
100                    }
101                }
102            },
103            error: function(xhr, status, error) {
104                reject(error);
105            }
106        });
107    });
108}
109
110function checkSessionState()
111{
112
113    fetch("/api/session-state").then((response) => {
114
115        response.json().then((parsedBody) => {
116
117            /* If we aren't signed in anymore - redirect. */
118            if (parsedBody["isLoggedIn"] === false)
119            {
120                localStorage.setItem("notifyExpiration", "true");
121                window.location.href = `/`;
122            }
123
124        });
125
126    });
127
128}
129
130function showSessionExpiredModal()
131{
132
133    const headerMeta = document.querySelector("meta[name='notify_expiration_header']");
134    const textMeta = document.querySelector("meta[name='notify_expiration_text']");
135
136    console.log(headerMeta);
137    console.log(textMeta);
138
139    if (!headerMeta || !textMeta)
140        return;
141
142    $("#global-modal-title").text(headerMeta.content);
143    $('#global-modal-body').text(textMeta.content);
144    $('#global-modal-element').modal('show');
145    $('#global-modal-footer button')[0].innerText = 'OK';
146
147    /* Remove tags from DOM. */
148    headerMeta.remove();
149    textMeta.remove();
150
151}
152
153function showLoginModalContent() {
154
155    /* Do we have loginPlatformChoice? - show it. */
156    if ($("#loginPlatformChoice").length)
157    {
158        $("#loginPlatformChoice").show();
159        $("#loginFormContainer").addClass("d-none");
160    }
161
162    // Hide step 1 and show the login form
163    $("[class^=register-step]").fadeOut(0, function() {
164        $(".login-step-1").hide().removeClass("d-none").fadeIn(0);
165    });
166
167    /* Show login bottom and hide registration bottom. */
168    $(".bot-login").show();
169    $(".bot-register").hide();
170}
171
172function showRegistrationModalContent() {
173
174    // Hide step 1 and show the registration form
175    $("[class^=login-step]").fadeOut(0, function() {
176        $(".register-step-1").hide().removeClass("d-none").fadeIn(0);
177    });
178
179    /* Show register bottom and hide login bottom. */
180    $(".bot-login").hide();
181    $(".bot-register").show();
182
183}
184function showAuthenticationModal() {
185    const authModal = new bootstrap.Modal('#authModal', {
186        keyboard: false
187    });
188    authModal.show();
189}
190
191function backgroundPusher() {
192    setInterval(() => {
193        $('.background-pusher').css({
194            "maxHeight": ($('.video-background').height() - $('header').height()) + "px"
195        });
196    }, 100);
197}
198
199document.addEventListener('DOMContentLoaded', (() => {
200
201    // Data layer events
202    createDataLayerEvent('pageView')
203
204    // Login
205    var urlParams = new URLSearchParams(window.location.search);
206    if(urlParams.has('loggedIn')) {
207        createDataLayerEvent('login');
208    }
209
210    // Transaction completed
211    if(window.location.href.indexOf('payment-complete') > -1 && urlParams.has('plan')) {
212        createDataLayerEvent('transactionCompleted');
213    }
214
215    // Watched asset
216    if(window.location.href.indexOf('watch/~') > -1) {
217        createDataLayerEvent('watchedAsset');
218    }
219
220    // Auth modal
221    const authModal = $("#authModal");
222    if(authModal.length) {
223
224        // Validation strings
225        const validationStrings = {
226            'en': {
227                'required': 'This field is required',
228                'password_required': 'Please provide a password',
229                'email': 'Please enter a valid email address',
230                'email_taken': 'This email address has already been taken. <a id="loginButton" class="text-reset" href="#">Click here to log in</a>.',
231                'jumpTOS': 'Agree with the TOS to create your account.',
232                'min_length': 'The password must be at least 6 characters long.'
233            },
234            'nl': {
235                'required': 'Dit veld is verplicht',
236                'password_required': 'Geef een wachtwoord op',
237                'email': 'Voer een geldig e-mailadres in',
238                'email_taken': 'Dit e-mailadres is al in gebruik. <a id="loginButton" class="text-reset" href="#">Klik hier om in te loggen</a>.',
239                'jumpTOS': 'Ga akkoord met de algemene voorwaarden om verder te gaan.',
240                'min_length': 'Het wachtwoord moet minimaal 6 tekens lang zijn.'
241            }
242        }
243
244        function escapeHtml(value) {
245            return String(value)
246                .replace(/&/g, '&amp;')
247                .replace(/</g, '&lt;')
248                .replace(/>/g, '&gt;')
249                .replace(/"/g, '&quot;')
250                .replace(/'/g, '&#039;');
251        }
252
253        function setLoginReturnUrl() {
254            const currentUrl = window.location.pathname + window.location.search;
255            const urlWithoutReturnUrl = currentUrl.split('&returnUrl=')[0].split('?returnUrl=')[0];
256            const loginForm = $(".login-step-1 form");
257            if (!loginForm.length) {
258                return;
259            }
260            loginForm.find('input[name="returnUrl"]').remove();
261            loginForm.append(
262                '<input type="hidden" name="returnUrl" value="' + encodeURIComponent(urlWithoutReturnUrl) + '">'
263            );
264        }
265
266        authModal[0].addEventListener('show.bs.modal', function () {
267            setLoginReturnUrl();
268        });
269
270        function showCustomTermsStep(response) {
271            const terms = response.terms || [];
272            const redirectUrl = response.redirect_to || "/";
273            const container = $("#customTermsLoginContainer");
274            container.html('');
275
276            terms.forEach(term => {
277                const termId = escapeHtml(term.id);
278                const termTitle = escapeHtml(term.title);
279                const termFile = escapeHtml(term.file);
280                container.append(`
281                    <div class="form-check mb-2">
282                        <input class="form-check-input custom-term-login-check" type="checkbox" value="on" name="custom_terms[${termId}]" id="customTermLogin${termId}" required="required">
283                        <label class="form-check-label" for="customTermLogin${termId}">
284                            Ik ga akkoord met de <a target="_blank" href="${termFile}">${termTitle}</a><span class="text-danger m-1">*</span>
285                        </label>
286                    </div>
287                `);
288            });
289
290            $("[class^=register-step], [class^=login-step]").fadeOut(0, function() {
291                $(".login-terms-step").hide().removeClass("d-none").fadeIn(0);
292            });
293
294            $(".login-terms-step form")
295                .off('submit.customTerms')
296                .on('submit.customTerms', function(event) {
297                    event.preventDefault();
298
299                    if ($(this).find('.custom-term-login-check:not(:checked)').length > 0) {
300                        $(this).find('.alert-danger').remove();
301                        $(this).prepend('<div class="alert alert-danger">Ga akkoord met de voorwaarden om verder te gaan.</div>');
302                        return;
303                    }
304
305                    const submitButton = $("#customTermsAcceptSubmit");
306                    submitButton.attr("disabled", "disabled");
307
308                    $.ajax({
309                        type: "POST",
310                        cache: false,
311                        url: "/api/custom-terms/accept",
312                        data: $(this).serialize(),
313                        success: () => {
314                            window.location = redirectUrl.indexOf('loggedIn=') === -1
315                                ? redirectUrl + (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1'
316                                : redirectUrl;
317                        },
318                        error: (jqXHR) => {
319                            const message = jqXHR.responseJSON?.message || 'Opslaan mislukt. Probeer het opnieuw.';
320                            $(".login-terms-step form").find('.alert-danger').remove();
321                            $(".login-terms-step form").prepend(`<div class="alert alert-danger">${message}</div>`);
322                        },
323                        complete: () => submitButton.removeAttr("disabled")
324                    });
325                });
326        }
327
328        // Step one logic
329        $("#registerStepOneSubmit").on('click', function () {
330
331            // Registration step 1 form validation
332            $(".register-step-1 form").validate({
333                rules: {
334                    email_address: {
335                        required: true,
336                        email: true
337                    },
338                    password: {
339                        required: true,
340                        minlength: 6,
341                        maxlength: 72,
342                    },
343                    jumpTOS: {
344                        required: true,
345                    }
346                },
347                messages: {
348                    password: {
349                        required: validationStrings[$('html').attr('lang')]['password_required'],
350                        minlength: validationStrings[$('html').attr('lang')]['min_length'],
351                    },
352                    jumpTOS: {
353                        required: validationStrings[$('html').attr('lang')]['jumpTOS']
354                    },
355                    email_address: validationStrings[$('html').attr('lang')]['email'],
356                },
357                submitHandler: function (form, event) {
358                    event.preventDefault();
359
360                    console.log('Submitting step one');
361
362                    // Hide step 1 and show step 2
363                    $(".register-step-1").fadeOut(500, function() {
364                        $(".register-step-2").hide().removeClass("d-none").fadeIn(500);
365                    });
366                }
367            });
368
369        });
370
371        // Step two logic
372        $("#registerStepTwoSubmit").on('click', function () {
373
374            // Registration step 1 form validation
375            $(".register-step-2 form").validate({
376                rules: {
377                    firstname: {
378                        required: true,
379                    },
380                    infix: {
381                        required: true,
382                    },
383                    lastname: {
384                        required: true,
385                    }
386                },
387                messages: {
388                    required: validationStrings[$('html').attr('lang')]['required'],
389                },
390                submitHandler: function (form, event) {
391                    event.preventDefault();
392
393                    // Hide step 2 and show step 3
394                    $(".register-step-2").fadeOut(500, function() {
395                        $(".register-step-3").hide().removeClass("d-none").fadeIn(500);
396                    });
397                }
398            });
399
400        });
401
402        // Login button
403        $("body").on('click', '#loginButton', function () {
404            showLoginModalContent();
405        });
406
407        // Register button
408        $("body").on('click', '#registerButton', function () {
409            showRegistrationModalContent();
410        });
411
412        // Navbar buttons and toggles
413        $("#authModalLogin, #authModalRegistration, .toggleLogin").on('click', function (e) {
414
415            // Check if clicked button is log in button or sign up
416            if($(e.target).attr('id') === 'authModalLogin' || $(e.target).hasClass("toggleLogin")) {
417                showLoginModalContent();
418                setLoginReturnUrl();
419            } else {
420                showRegistrationModalContent();
421            }
422
423            // Show the authentication modal
424            showAuthenticationModal();
425
426        });
427
428        // Login form validation
429        $(".login-step-1 form").validate({
430            rules: {
431                email_address: {
432                    required: true,
433                    email: true
434                },
435                password: {
436                    required: true,
437                }
438            },
439            messages: {
440                password: {
441                    required: validationStrings['en']['required']
442                },
443                email_address: validationStrings[$('html').attr('lang')]['email']
444            }
445        });
446
447        /* Handle loginWithPlatformButton. */
448        $("#loginWithPlatformButton").click(() => {
449            $("#loginFormContainer").removeClass("d-none");
450            $("#loginPlatformChoice").hide();
451        });
452
453        /* Handle login form submissions. */
454        $(".login-step-1 form").submit(e => {
455
456            /* Prevent default action. */
457            e.preventDefault();
458
459            /* Perform data serialization. */
460            const formData = $(e.currentTarget).serialize();
461            
462            // Extract returnUrl from form data or use current URL
463            const formDataObj = new URLSearchParams(formData);
464            let returnUrl = formDataObj.get('returnUrl');
465            
466            // If no returnUrl in form, use current URL
467            if (!returnUrl) {
468                const currentUrl = window.location.pathname + window.location.search;
469                const urlWithoutReturnUrl = currentUrl.split('&returnUrl=')[0].split('?returnUrl=')[0];
470                returnUrl = urlWithoutReturnUrl;
471            }
472
473            /* Retrieve the elements we want to disable. */
474            const elements = $(".login-step-1 input, #logInStepOneSubmit");
475
476            /* Disable all forms to indicate a network request is being done. */
477            elements.attr("disabled", "disabled");
478
479            /* Send a POST request to the login API with returnUrl as query parameter */
480            $.ajax({
481                type: "POST",
482                cache: false,
483                url: "/api/login" + (returnUrl ? "?returnUrl=" + encodeURIComponent(returnUrl) : ""),
484                data: formData,
485                dataType: 'json', // Expect JSON response
486                timeout: 30000, // 30 second timeout
487
488                /* Handle success - redirect based on returnUrl or home page. */
489                success: (response) => {
490                    console.log("Login success response:", response);
491                    if (response && response.terms_required) {
492                        showCustomTermsStep(response);
493                        return;
494                    }
495
496                    /* Check if API returned a redirect URL (returnUrl) */
497                    let redirectUrl = "/";
498                    if (response && response.redirect_to) {
499                        redirectUrl = response.redirect_to;
500                        /* Add loggedIn parameter if not already present */
501                        if (redirectUrl.indexOf('loggedIn=') === -1) {
502                            redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1';
503                        }
504                    } else {
505                        /* Check for returnUrl in current URL query parameters */
506                        const urlParams = new URLSearchParams(window.location.search);
507                        const returnUrl = urlParams.get('returnUrl');
508                        if (returnUrl) {
509                            redirectUrl = decodeURIComponent(returnUrl);
510                            if (redirectUrl.indexOf('loggedIn=') === -1) {
511                                redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1';
512                            }
513                        }
514                    }
515                    window.location = redirectUrl;
516                },
517
518                /* Show errors to the user when they occur. */
519                error: (jqXHR) => {
520                    console.error("Login error:", jqXHR);
521                    console.error("Status:", jqXHR.status);
522                    console.error("Status Text:", jqXHR.statusText);
523                    console.error("Response Text:", jqXHR.responseText);
524                    console.error("Response JSON:", jqXHR.responseJSON);
525                    
526                    /* Check if response might be valid JSON but status is not 200 (e.g., PHP warnings corrupted response) */
527                    if (jqXHR.responseText) {
528                        try {
529                            const parsed = JSON.parse(jqXHR.responseText);
530                            console.log("Parsed response:", parsed);
531                            
532                            /* If response has redirect_to or status 200, it's actually a success */
533                            if (parsed && (parsed.redirect_to || parsed.status === 200)) {
534                                console.log("Login successful (detected in error handler), redirecting...");
535                                let redirectUrl = parsed.redirect_to || "/";
536                                if (redirectUrl.indexOf('loggedIn=') === -1) {
537                                    redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1';
538                                }
539                                window.location = redirectUrl;
540                                return; // Don't show error
541                            }
542                        } catch (e) {
543                            console.error("Failed to parse response as JSON:", e);
544                        }
545                    }
546                    
547                    /* Check if response is JSON and has errors */
548                    let errors = {};
549                    
550                    if (jqXHR.responseJSON && jqXHR.responseJSON.errors) {
551                        errors = jqXHR.responseJSON.errors;
552                    } else if (jqXHR.responseJSON && jqXHR.responseJSON.error_details) {
553                        /* Handle error_details format */
554                        errors = {
555                            "general": jqXHR.responseJSON.error_details || "An error occurred during login. Please try again."
556                        };
557                    } else if (jqXHR.responseText) {
558                        /* Try to parse response text as JSON */
559                        try {
560                            const parsed = JSON.parse(jqXHR.responseText);
561                            if (parsed.errors) {
562                                errors = parsed.errors;
563                            } else if (parsed.error_details) {
564                                errors = {
565                                    "general": parsed.error_details || "An error occurred during login. Please try again."
566                                };
567                            }
568                        } catch (e) {
569                            /* Not JSON, use generic error */
570                            errors = {
571                                "general": "An error occurred during login. Please try again."
572                            };
573                        }
574                    } else {
575                        /* No response, use generic error */
576                        errors = {
577                            "general": "An error occurred during login. Please try again."
578                        };
579                    }
580                    
581                    /* Show server response to the user. */
582                    const $loginForm = $(".login-step-1 form");
583                    
584                    /* Remove any existing error messages */
585                    $loginForm.find('.alert-danger').remove();
586                    $loginForm.find('.is-invalid').removeClass('is-invalid');
587                    
588                    /* Try to use jQuery Validate if available and properly initialized */
589                    try {
590                        if ($loginForm.length && $loginForm.data('validator')) {
591                            /* Form has validator initialized, use it */
592                            const validator = $loginForm.validate();
593                            if (validator && typeof validator.showErrors === 'function') {
594                                /* Ensure errors object has correct format for jQuery Validate */
595                                const formattedErrors = {};
596                                for (const [field, message] of Object.entries(errors)) {
597                                    /* Map field names to actual form field names */
598                                    const fieldName = field === 'general' ? 'email_address' : field;
599                                    formattedErrors[fieldName] = message;
600                                }
601                                validator.showErrors(formattedErrors);
602                                return; /* Successfully showed errors */
603                            }
604                        }
605                    } catch (e) {
606                        console.error("Error showing validation errors:", e);
607                        /* Fall through to manual error display */
608                    }
609                    
610                    /* Fallback: show errors manually */
611                    console.error("Login errors:", errors);
612                    let errorHtml = '<div class="alert alert-danger alert-dismissible fade show" role="alert"><ul class="mb-0">';
613                    for (const [field, message] of Object.entries(errors)) {
614                        errorHtml += `<li>${message}</li>`;
615                    }
616                    errorHtml += '</ul><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
617                    
618                    if ($loginForm.length) {
619                        $loginForm.prepend(errorHtml);
620                    } else {
621                        $(".login-step-1").prepend(errorHtml);
622                    }
623
624                },
625
626                /* Enable the fields again as the network request has finished pending. */
627                complete: () => elements.removeAttr("disabled")
628
629            });
630
631        });
632
633        /* Handle registration form submissions. */
634        $("#registerStepThreeSubmit, #registerStepThreeFinalSubmit").click(e => {
635
636            /* Disable the button we have just pressed - to indicate we are performing a network request. */
637            $("#registerStepThreeSubmit, #registerStepThreeFinalSubmit, #loginButton").attr("disabled", "disabled");
638
639            /* Disable the user's ability to click on login - we are processing. */
640            $("#loginButton").hide();
641
642            /* Retrieve data from all the forms. */
643            const step1FormData = $(".register-step-1 form")
644                .serializeArray()
645                .reduce((obj, { name, value }) => (obj[name] = value, obj), {});
646
647            const step2FormData = $(".register-step-2 form")
648                .serializeArray()
649                .reduce((obj, { name, value }) => {
650                    if (obj[name]) {
651                        if (!Array.isArray(obj[name])) {
652                            obj[name] = [obj[name]];
653                        }
654                        obj[name].push(value);
655                    } else {
656                        obj[name] = value;
657                    }
658                    return obj;
659                }, {});
660
661            const step3FormData = $(".register-step-3 form")
662                .serializeArray()
663                .reduce((obj, { name, value }) => (obj[name] = value, obj), {});
664
665            /* Combine all the data from the forms. */
666            const combinedFormData = {
667                ...step1FormData,
668                ...step2FormData,
669                ...step3FormData
670            };
671
672            /* Perform the registration request. */
673            $.ajax({
674                type: "POST",
675                cache: false,
676                url: "/api/register",
677                data: combinedFormData,
678
679                /* Handle success states. */
680                success: (response) => {
681
682                    /* Check if API returned a redirect URL (e.g., return_to) */
683                    let redirectUrl = "/first-logon-survey";
684                    if (response && response.redirect_to) {
685                        redirectUrl = response.redirect_to;
686                        /* Add loggedIn parameter if not already present */
687                        if (redirectUrl.indexOf('loggedIn=') === -1) {
688                            redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1';
689                        }
690                    }
691
692                    /* Redirect to the appropriate page */
693                    window.location = redirectUrl;
694
695                },
696
697                /* Handle form errors. */
698                error: (jqXHR) => {
699                    console.error("Registration error:", jqXHR);
700                    console.error("Status:", jqXHR.status);
701                    console.error("Status Text:", jqXHR.statusText);
702                    console.error("Response Text:", jqXHR.responseText);
703                    console.error("Response JSON:", jqXHR.responseJSON);
704                    
705                    /* Parse errors from response */
706                    let errors = {};
707                    
708                    if (jqXHR.responseJSON && jqXHR.responseJSON.errors) {
709                        errors = jqXHR.responseJSON.errors;
710                    } else if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
711                        // Handle single error message
712                        errors = {
713                            "general": jqXHR.responseJSON.error
714                        };
715                    } else if (jqXHR.responseText) {
716                        try {
717                            const parsed = JSON.parse(jqXHR.responseText);
718                            if (parsed.errors) {
719                                errors = parsed.errors;
720                            } else if (parsed.error) {
721                                errors = {
722                                    "general": parsed.error
723                                };
724                            } else if (parsed.message) {
725                                errors = {
726                                    "general": parsed.message
727                                };
728                            }
729                        } catch (e) {
730                            console.error("Failed to parse error response:", e);
731                            /* Not JSON, use generic error */
732                            errors = {
733                                "general": "An error occurred during registration. Please try again."
734                            };
735                        }
736                    } else {
737                        // No response text, check status code
738                        if (jqXHR.status === 0) {
739                            errors = {
740                                "general": "Network error. Please check your internet connection."
741                            };
742                        } else if (jqXHR.status === 500) {
743                            errors = {
744                                "general": "Server error. Please try again later."
745                            };
746                        } else {
747                            errors = {
748                                "general": "An error occurred during registration. Please try again."
749                            };
750                        }
751                    }
752                    
753                    /* Remove existing error messages */
754                    $(".register-step-2 form").find('.alert-danger').remove();
755                    $(".register-step-2 form").find('.is-invalid').removeClass('is-invalid');
756                    
757                    /* Try to use jQuery Validate if available */
758                    try {
759                        const $registerForm = $(".register-step-2 form");
760                        if ($registerForm.length && $registerForm.data('validator')) {
761                            const validator = $registerForm.validate();
762                            if (validator && typeof validator.showErrors === 'function') {
763                                validator.showErrors(errors);
764                                return; /* Successfully showed errors */
765                            }
766                        }
767                    } catch (e) {
768                        console.error("Error showing validation errors:", e);
769                        /* Fall through to manual error display */
770                    }
771                    
772                    /* Fallback: show errors manually */
773                    let errorHtml = '<div class="alert alert-danger alert-dismissible fade show" role="alert"><ul class="mb-0">';
774                    for (const [field, message] of Object.entries(errors)) {
775                        errorHtml += `<li>${message}</li>`;
776                    }
777                    errorHtml += '</ul><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
778                    $(".register-step-2 form").prepend(errorHtml);
779
780                },
781
782                /* Handle the finishing of the network request by enabling inputs again. */
783                complete: () => {
784
785                    /* Enable buttons again. */
786                    $("#registerStepThreeSubmit, #registerStepThreeFinalSubmit, #loginButton").removeAttr("disabled");
787
788                    /* Show the login button again. */
789                    $("#loginButton").show();
790
791                }
792            });
793
794        });
795
796        /**
797         * Checks email availability.
798         */
799        const checkEmailAvailability = (emailAddress, error_text) => {
800
801            /* Remove the emailTakenError element and enable the button again. */
802            $(".emailTakenError").remove();
803            $("#registerStepOneSubmit").removeAttr("disabled");
804
805            /**
806             * Checks whether the emailAddress value is a val]id email address to ensure we only check valid email addresses.
807             * @param emailAddress
808             * @returns {boolean}
809             */
810            const isValidEmail = (emailAddress) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailAddress);
811
812            /* If the emailAddress variable is not a valid email address yet, we do not bother to continue. */
813            if (!isValidEmail(emailAddress))
814            {
815                return;
816            }
817
818            /* Perform a GET request to the email availability endpoint. */
819            $.get(`/api/email-availability?email_address=${emailAddress}`, data => {
820
821                /* If the email is not available - show an error message on the email address input field. */
822                if (!data.available)
823                {
824                    $("#emailInput").after(`<span class="text-danger emailTakenError small">${error_text}</span>`);
825
826                    /* Disable the submission button - until the user solves this error. */
827                    $("#registerStepOneSubmit").attr("disabled", "disabled");
828                }
829
830            });
831
832        }
833
834        /* Variable to store the last entered email address. */
835        let lastCheckedEmailAddress = "";
836
837        /* Listen to input events on the email address field. */
838        $(".register-step-1 input[name='email_address']").on("keydown blur", () => {
839
840            /* Retrieve the current value. */
841            const emailAddress = $(".register-step-1 input[name='email_address']").val();
842
843            /* If the la
843stCheckedEmailAddress is equal to the current email address - do not continue. */
844            if (emailAddress === lastCheckedEmailAddress)
845            {
846                return;
847            }
848
849            /* Check lastCheckedEmailAddress here. */
850            lastCheckedEmailAddress = emailAddress;
851
852            /* Determine the error validation string */
853            var validationString = validationStrings[$('html').attr('lang')]['email_taken'];
854
855            /* Check email address availability. */
856            checkEmailAvailability(emailAddress, validationString);
857
858        });
859
860    }
861
862    $('.glide').not('[data-collection-carousel-glide]').each(function() {
863
864        const slides = $(this).find('li.glide__slide').length + $('.glide__slides div').length;
865        var autoplaySetting = $(this).parents('.video-content').length && slides > 2 ? 2000 : false;
866        var amountOfSlidesVisible = $(this).data('amount-of-slides-visible');
867
868        // Fallback for empty amounts
869        if(!amountOfSlidesVisible) {
870            amountOfSlidesVisible = 4;
871        }
872
873        const config = {
874
875            startAt: 0,
876            perView: amountOfSlidesVisible,
877            autoplay: autoplaySetting,
878            type: 'carousel',
879            controls: true,
880
881            breakpoints: {
882
883                600: {
884                    perView: 1.25,
885                    autoplay: slides > 1 ? 5000 : false
886                },
887
888                1200: {
889                    perView: 2.25,
890                    autoplay: autoplaySetting,
891                }
892
893            }
894
895        };
896
897        new Glide(this, config).mount()
898
899    });
900
901    backgroundPusher();
902
903    // Preview video only on desktop/laptop (not loaded on mobile/tablet for speed; first tap goes to card)
904    var isDesktop = window.matchMedia('(min-width: 992px)').matches && !('ontouchstart' in window);
905
906    $('.on-demand-video').each(function() {
907        var videoElement = $(this).find('video');
908        var videoUrl = videoElement.attr('data-src');
909        if (!videoUrl) return;
910
911        if (!isDesktop) return;
912
913        $.ajax({
914            url: videoUrl,
915            success: function() {
916                videoElement.attr('src', videoUrl);
917                videoElement.closest('.on-demand-video').on('mouseover', function() {
918                    $(this).find('video').removeClass('d-none').trigger('play');
919                    $(this).find('img').addClass('d-none');
920                }).on('mouseleave', function() {
921                    $(this).find('video').addClass('d-none').trigger('pause');
922                    $(this).find('img').removeClass('d-none');
923                });
924            },
925            fail: function() {
926                console.error('Test!');
927            }
928        });
929    });
930
931    $('[data-bs-target="#searchBarModal"]').on('click', function(e) {
932        $("input[name=query]").focus();
933    });
934
935    // Copy link
936    $(".copy-link").click(function(){
937        // Create a temporary input element to hold the URL
938        var tempInput = $("<input>");
939        $("body").append(tempInput);
940        tempInput.val(window.location.href).select();
941
942        // Copy the URL to the clipboard
943        document.execCommand("copy");
944
945        // Remove the temporary input element
946        tempInput.remove();
947
948        // Temporarily change the icon to let the user know the link has been copied
949        var button = $(this);
950        button.html('<i class="fas fa-check"></i>');
951        setTimeout(function () {
952            button.html('<i class="fas fa-link"></i>');
953        }, 1000)
954    });
955
956    // Enable tooltips
957    const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
958    const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
959
960    // Tag overview: hide "load more" button when there is nothing more (data-has-more="0")
961    $('.js-tags-overview-section[data-has-more="0"] .js-tags-overview-load-more').closest('.text-center').hide();
962
963    // Tag overview: "Next video's" load more
964    $(document).on('click', '.js-tags-overview-load-more', function() {
965        var btn = $(this);
966        var section = btn.closest('.js-tags-overview-section');
967        if (!section.length) return;
968        var tag = section.data('tag');
969        var offset = parseInt(section.data('offset'), 10) || 24;
970        var total = parseInt(section.data('total'), 10) || 0;
971        var limit = 24;
972        var sort = section.data('sort') || 'created_date:asc';
973        var assetType = section.data('asset-type') || 'all';
974        var aspectRatio = section.data('aspect-ratio') || '16/9';
975        var carousel = section.data('carousel') === 1 || section.data('carousel') === '1';
976        var slidesContainer = section.find('.js-tags-overview-slides');
977        if (!slidesContainer.length || !tag) return;
978        btn.prop('disabled', true);
979        var url = '/api/tag-overview-more?tag=' + encodeURIComponent(tag) + '&offset=' + offset + '&limit=' + limit + '&sort=' + encodeURIComponent(sort) + '&asset_type_filter=' + encodeURIComponent(assetType) + '&aspect_ratio=' + encodeURIComponent(aspectRatio) + '&carousel=' + (carousel ? '1' : '0');
980        $.get(url)
981            .done(function(html, textStatus, jqXHR) {
982                slidesContainer.append(html);
983                var count = parseInt(jqXHR && jqXHR.getResponseHeader('X-Tag-Overview-Count'), 10);
984                if (isNaN(count)) count = 0;
985                var newOffset = offset + count;
986                section.data('offset', newOffset);
987                /* Hide when no more items, or when we have a total and reached it */
988                if (count < limit || (total > 0 && newOffset >= total)) {
989                    section.data('has-more', '0');
990                    btn.closest('.text-center').hide();
991                }
992                if (carousel && typeof Glide !== 'undefined') {
993                    var glideEl = section.find('.glide')[0];
994                    if (glideEl && glideEl.__glide && typeof glideEl.__glide.update === 'function') {
995                        glideEl.__glide.update();
996                    }
997                }
998            })
999            .fail(function() {
1000                btn.prop('disabled', false);
1001            })
1002            .always(function() {
1003                btn.prop('disabled', false);
1004            });
1005    });
1006
1007}));
1008
1009$(document).ready(function() {
1010
1011    /* If we have to show the session expiration modal - do so now. */
1012    if (window.notifySessionExpiration || localStorage.getItem("notifyExpiration"))
1013    {
1014
1015        /* Show expiration modal. */
1016        showSessionExpiredModal();
1017
1018        /* If we have the notifyExpiration query string - remove it now. */
1019        if (localStorage.getItem("notifyExpiration"))
1020        {
1021            localStorage.removeItem("notifyExpiration");
1022        }
1023
1024        return;
1025    }
1026
1027    if (window.loggedIn)
1028    {
1029        const SESSION_STATE_INTERVAL_MS = 30000;
1030        let sessionStateTimer = null;
1031
1032        function startSessionStatePolling() {
1033            if (sessionStateTimer) {
1034                return;
1035            }
1036            checkSessionState();
1037            sessionStateTimer = setInterval(checkSessionState, SESSION_STATE_INTERVAL_MS);
1038        }
1039
1040        function stopSessionStatePolling() {
1041            if (!sessionStateTimer) {
1042                return;
1043            }
1044            clearInterval(sessionStateTimer);
1045            sessionStateTimer = null;
1046        }
1047
1048        startSessionStatePolling();
1049
1050        document.addEventListener('visibilitychange', function () {
1051            if (document.hidden) {
1052                stopSessionStatePolling();
1053            } else {
1054                startSessionStatePolling();
1055            }
1056        });
1057    }
1058
1059});

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.