PageSourceSearch

https://markbornformn.com/scripts/main.js?v5

js markbornformn.com collected 2026-10-05 18:58:01 UTC 19,056 bytes, 500 lines download raw bytes

1/**
2 * main.js - Main site functionality 
3 * Handles form submissions, page interactions, and UI effects
4 */
5
6let pageLoadTime = Date.now();
7let challengeCompleted = false;
8
9$(document).ready(function () {
10    // External link handler - opens external links in new window, except campaignpartner.net
11    const links = document.querySelectorAll('a[href^="http://"], a[href^="https://"]');
12    links.forEach(link => {
13        if (link.hostname &&
14            link.hostname !== location.hostname &&
15            !link.hostname.endsWith('campaignpartner.net')) {
16            link.target = '_blank';
17        }
18    });
19
20    // Initialize YouTube Content Fixer
21    YouTubeContentFixer.init();
22
23    // Form submission handler
24    $("#subscription").submit(function (j) {
25        j.preventDefault();
26
27        var currentTime = Date.now();
28        var elapsedTime = currentTime - pageLoadTime;
29        var obfuscatedTime = btoa(elapsedTime.toString());
30        var encodedTimestamp = btoa(currentTime.toString());
31
32        var c = $(this),
33            e = c.find('input[name="e"]').val(),
34            i = c.find('input[name="z"]').val(),
35            b = c.find('input[name="f"]').val(),
36            k = c.find('input[name="l"]').val();
37
38        $.post("/EmailSignup.ashx", {
39            e: e,
40            z: i,
41            f: b,
42            l: k,
43            t: obfuscatedTime,
44            ts: encodedTimestamp
45        }, function (h) {
46            var g = $(h).find("#formResponse");
47            $("#result").html(g);
48        }).fail(function (xhr, status, error) {
49            console.log("Error: " + error);
50        });
51    });
52
53    // Scroll to top functionality
54    var $backToTop = $('#backToTop');
55    var backToTopShown = false;
56
57    $(window).on('scroll', function () {
58        var shouldShow = $(this).scrollTop() > 0;   // > 0 ignores iOS's negative bounce offsets
59        if (shouldShow !== backToTopShown) {
60            backToTopShown = shouldShow;
61            $backToTop.stop(true)[shouldShow ? 'fadeIn' : 'fadeOut'](400);
62        }
63    });
64
65    $backToTop.on('click', function (e) {
66        e.preventDefault();
67        // Let the browser animate the scroll itself. On iOS, a jQuery scrollTop
68        // tween competes with Safari's native scrolling and can bounce back.
69        if ('scrollBehavior' in document.documentElement.style) {
70            window.scrollTo({ top: 0, behavior: 'smooth' });
71        } else {
72            $('html, body').stop().animate({ scrollTop: 0 }, 800);   // older browsers
73        }
74    });
75});
76
77// Also run when window fully loads (in case of late-loading content)
78window.addEventListener('load', function () {
79    setTimeout(() => {
80        YouTubeContentFixer.fixYouTubeContent();
81    }, 1000);
82});
83
84
85// Challenge completion tracker
86function completeChallenge() {
87    challengeCompleted = true;
88}
89
90setTimeout(completeChallenge, 3000);
91
92// Utility functions
93function encodeValue(value) {
94    return btoa(value.toString());
95}
96
97function isValidEmail(email) {
98    const re = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
99    return re.test(email);
100}
101
102// Form submission function
103function subForm() {
104    const subEElement = document.querySelector('#subE');
105    const subZElement = document.querySelector('#subZ');
106    const subFElement = document.querySelector('#subF');
107    const subLElement = document.querySelector('#subL');
108    const subPElement = document.querySelector('#subP');
109    const messageElement = document.querySelector('#subH');
110    const disclaimerCheckbox = document.querySelector('#disclaimerCheckbox');
111    let isFormValid = true;
112
113    const params = {
114        email: subEElement && subEElement.value.trim() ? subEElement.value.trim() : '',
115        zip: subZElement && subZElement.value.trim() ? subZElement.value.trim() : '',
116        first: subFElement && subFElement.value.trim() ? subFElement.value.trim() : '',
117        last: subLElement && subLElement.value.trim() ? subLElement.value.trim() : '',
118        phone: subPElement && subPElement.value.trim() ? subPElement.value.trim() : '',
119        message: messageElement && messageElement.value.trim() ? messageElement.value.trim() : ''
120    };
121
122    // SMS opt-in: if the disclaimer checkbox carries a data-list-id,
123    // it's the centralized SMS opt-in (text was overwritten by the server).
124    // Send the checked state and list ID so the handler can tag the contact.
125    const disclaimerCb = document.querySelector('#disclaimerCheckbox');
126    if (disclaimerCb && disclaimerCb.getAttribute('data-list-id')) {
127        params.smsOptIn = disclaimerCb.checked;
128        params.smsListId = parseInt(disclaimerCb.getAttribute('data-list-id'), 10) || 0;
129    }
130
131    [subEElement, subZElement, subFElement, subLElement].forEach(field => {
132        if (field && field.classList.contains('required') && !field.value.trim()) {
133            field.classList.add("error");
134            isFormValid = false;
135        } else if (field) {
136            field.classList.remove("error");
137        }
138    });
139
140    if (subEElement && subEElement.value.trim() && !isValidEmail(subEElement.value)) {
141        subEElement.classList.add("error");
142        isFormValid = false;
143    }
144
145    if (disclaimerCheckbox && !disclaimerCheckbox.checked) {
146        isFormValid = false;
147        disclaimerCheckbox.classList.add("error");
148    } else if (disclaimerCheckbox) {
149        disclaimerCheckbox.classList.remove("error");
150    }
151
152    const currentTime = Date.now();
153    const elapsedTime = currentTime - pageLoadTime;
154    const obfuscatedTime = btoa(elapsedTime.toString());
155    const encodedTimestamp = btoa(currentTime.toString());
156    params.t = obfuscatedTime;
157    params.ts = encodedTimestamp;
158
159    if (isFormValid) {
160        fetch('/EmailSignup', {
161            method: 'POST',
162            headers: {
163                'Content-Type': 'application/json'
164            },
165            body: JSON.stringify(params)
166        })
167            .then(response => {
168                // Non-2xx responses (e.g. a 405 from the routing middleware on
169                // suspended/unpublished sites) may have an empty or non-JSON
170                // body; parse only when the server says it sent JSON so we fail
171                // with a clear error instead of a JSON.parse exception.
172                const contentType = response.headers.get('content-type') || '';
173                if (contentType.includes('application/json')) {
174                    return response.json();
175                }
176                if (!response.ok) {
177                    throw new Error('Signup request failed: HTTP ' + response.status);
178                }
179                throw new Error('Signup request returned an unexpected response.');
180            })
181            .then(data => {
182                if (data.success) {
183                    $("#emailSignup").hide();
184                    $("#signupThanks").show();
185                } else {
186                    console.log("Error: " + (data.error || "Unknown error occurred"));
187                    // Tell the widget's button wrapper the attempt is over so it
188                    // can restore the button immediately instead of waiting for
189                    // its 10-second backstop.
190                    document.dispatchEvent(new CustomEvent('signup:error'));
191                    alert(data.error || "An error occurred. Please try again.");
192                }
193            })
194            .catch(error => {
195                console.error('Fetch error:', error);
196                document.dispatchEvent(new CustomEvent('signup:error'));
197                alert("An error occurred. Please try again.");
198            });
199
200        // A request is in flight: the button wrapper may enter its
201        // "Submitting…" state.
202        return true;
203    } else {
204        if (disclaimerCheckbox && !disclaimerCheckbox.checked) {
205            alert('Please agree to the disclaimer before submitting.');
206        } else {
207            alert('Please correct the errors in the form.');
208        }
209
210        // Validation failed, nothing was sent: the button wrapper must NOT
211        // enter its "Submitting…" state (a disabled button here left the
212        // form dead for 10 seconds after an unchecked required checkbox).
213        return false;
214    }
215}
216
217// Sidebar email signup function
218function sidebarSubForm() {
219    const sidebarSubEElement = document.querySelector('#sidebarSubE');
220    const sidebarSubButton = document.querySelector('#sidebarSubButton');
221    let isFormValid = true;
222
223    const params = {
224        email: sidebarSubEElement && sidebarSubEElement.value.trim() ? sidebarSubEElement.value.trim() : '',
225        zip: '',
226        first: '',
227        last: '',
228        phone: '',
229        message: ''
230    };
231
232    if (!params.email) {
233        sidebarSubEElement.classList.add("error");
234        isFormValid = false;
235    } else if (!isValidEmail(params.email)) {
236        sidebarSubEElement.classList.add("error");
237        isFormValid = false;
238    } else {
239        sidebarSubEElement.classList.remove("error");
240    }
241
242    const currentTime = Date.now();
243    const elapsedTime = currentTime - pageLoadTime;
244    params.t = btoa(elapsedTime.toString());
245    params.ts = btoa(currentTime.toString());
246
247    if (isFormValid) {
248        sidebarSubButton.disabled = true;
249        sidebarSubButton.value = "...";
250
251        fetch('/EmailSignup', {
252            method: 'POST',
253            headers: {
254                'Content-Type': 'application/json'
255            },
256            body: JSON.stringify(params)
257        })
258            .then(response => {
259                // Same guard as subForm(): only parse JSON when the server
260                // actually sent JSON; surface everything else as an error.
261                const contentType = response.headers.get('content-type') || '';
262                if (contentType.includes('application/json')) {
263                    return response.json();
264                }
265                if (!response.ok) {
266                    throw new Error('Signup request failed: HTTP ' + response.status);
267                }
268                throw new Error('Signup request returned an unexpected response.');
269            })
270            .then(data => {
271                if (data.success) {
272                    $("#sidebarEmailSignup").hide();
273                    $("#sidebarSignupThanks").show();
274                } else {
275                    sidebarSubButton.disabled = false;
276                    sidebarSubButton.value = "GO";
277                    alert(data.error || "An error occurred. Please try again.");
278                }
279            })
280            .catch(error => {
281                sidebarSubButton.disabled = false;
282                sidebarSubButton.value = "GO";
283                console.error('Fetch error:', error);
284                alert("An error occurred. Please try again.");
285            });
286    } else {
287        alert('Please enter a valid email address.');
288    }
289}
290
291/**
292* YouTube Content Fixer - Automatically converts YouTube embeds to privacy-enhanced mode
293* and adds appropriate referrer policies
294*/
295const YouTubeContentFixer = {
296    /**
297     * Main function to fix all YouTube content on the page
298     */
299    fixYouTubeContent: function () {
300        const iframes = document.querySelectorAll('iframe');
301
302        iframes.forEach(iframe => {
303            const src = iframe.getAttribute('src');
304            if (src && (src.includes('youtube.com') || src.includes('youtu.be'))) {
305                // Create a completely new iframe
306                const newIframe = document.createElement('iframe');
307
308                // Copy all attributes EXCEPT src
309                for (let attr of iframe.attributes) {
310                    if (attr.name !== 'src') {
311                        newIframe.setAttribute(attr.name, attr.value);
312                    }
313                }
314
315                // Set referrer policy FIRST (before src)
316                newIframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
317
318                // Then set the corrected src
319                const fixedUrl = this.convertToNoCookie(src);
320                newIframe.setAttribute('src', fixedUrl);
321
322                // Replace old iframe with new one
323                iframe.parentNode.replaceChild(newIframe, iframe);
324            }
325        });
326    },
327
328    /**
329     * Fixes YouTube URL in an iframe element
330     */
331    fixYouTubeUrl: function (iframe) {
332        const src = iframe.getAttribute('src');
333        if (!src) return false;
334
335        const fixedUrl = this.convertToNoCookie(src);
336        if (fixedUrl !== src) {
337            iframe.setAttribute('src', fixedUrl);
338            return true;
339        }
340        return false;
341    },
342
343    /**
344     * Converts a YouTube URL to youtube-nocookie.com
345     */
346    convertToNoCookie: function (url) {
347        let fixedUrl = url;
348
349        // Handle youtu.be shortened URLs
350        if (url.includes('youtu.be/')) {
351            const videoIdMatch = url.match(/youtu\.be\/([a-zA-Z0-9_-]+)(\?.*)?$/);
352            if (videoIdMatch) {
353                const videoId = videoIdMatch[1];
354                const params = videoIdMatch[2] || '';
355                fixedUrl = `https://www.youtube-nocookie.com/embed/${videoId}${params}`;
356            }
357        }
358
359        // Replace various YouTube URL patterns
360        const replacements = [
361            { from: 'https://www.youtube.com/embed/', to: 'https://www.youtube-nocookie.com/embed/' },
362            { from: 'https://youtube.com/embed/', to: 'https://www.youtube-nocookie.com/embed/' },
363            { from: 'http://www.youtube.com/embed/', to: 'https://www.youtube-nocookie.com/embed/' },
364            { from: 'http://youtube.com/embed/', to: 'https://www.youtube-nocookie.com/embed/' },
365            { from: '//www.youtube.com/embed/', to: '//www.youtube-nocookie.com/embed/' },
366            { from: '//youtube.com/embed/', to: '//www.youtube-nocookie.com/embed/' },
367            { from: 'youtube.com/v/', to: 'youtube-nocookie.com/embed/' }
368        ];
369
370        replacements.forEach(replacement => {
371            if (fixedUrl.includes(replacement.from)) {
372                fixedUrl = fixedUrl.replace(replacement.from, replacement.to);
373            }
374        });
375
376        // Handle src="youtube.com/embed/" without protocol
377        if (fixedUrl.match(/^(?!https?:\/\/)(?!\/\/)(www\.)?youtube\.com\/embed\//)) {
378            fixedUrl = fixedUrl.replace(/^(?:www\.)?youtube\.com\/embed\//, 'https://www.youtube-nocookie.com/embed/');
379        }
380
381        return fixedUrl;
382    },
383
384    /**
385     * Adds referrer policy to iframe if it contains youtube-nocookie and doesn't have one
386     */
387    addReferrerPolicy: function (iframe) {
388        const src = iframe.getAttribute('src');
389        const dataSrc = iframe.getAttribute('data-src');
390
391        // Check if iframe has youtube-nocookie.com in src or data-src
392        const hasYouTubeNoCookie = (src && src.includes('youtube-nocookie.com')) ||
393            (dataSrc && dataSrc.includes('youtube-nocookie.com'));
394
395        if (hasYouTubeNoCookie && !iframe.hasAttribute('referrerpolicy')) {
396            iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
397            return true;
398        }
399        return false;
400    },
401
402    /**
403     * Analyze content (for debugging)
404     */
405    analyzeContent: function () {
406        const analysis = {
407            totalIframes: 0,
408            needingUrlFix: 0,
409            needingReferrerPolicy: 0,
410            iframes: []
411        };
412
413        const iframes = document.querySelectorAll('iframe');
414
415        iframes.forEach((iframe, index) => {
416            const src = iframe.getAttribute('src') || '';
417            const dataSrc = iframe.getAttribute('data-src') || '';
418            const combinedSrc = src || dataSrc;
419
420            const iframeInfo = {
421                index: index,
422                src: combinedSrc,
423                domain: '',
424                videoId: '',
425                hasReferrerPolicy: iframe.hasAttribute('referrerpolicy'),
426                needsUrlFix: false,
427                needsReferrerPolicy: false
428            };
429
430            // Determine domain and if fixes are needed
431            if (combinedSrc.includes('youtube-nocookie.com')) {
432                iframeInfo.domain = 'youtube-nocookie.com';
433                iframeInfo.needsReferrerPolicy = !iframeInfo.hasReferrerPolicy;
434            } else if (combinedSrc.includes('youtube.com')) {
435                iframeInfo.domain = 'youtube.com';
436                iframeInfo.needsUrlFix = true;
437                iframeInfo.needsReferrerPolicy = true;
438            } else if (combinedSrc.includes('youtu.be')) {
439                iframeInfo.domain = 'youtu.be';
440                iframeInfo.needsUrlFix = true;
441                iframeInfo.needsReferrerPolicy = true;
442            }
443
444            // Extract video ID
445            const videoIdMatch = combinedSrc.match(/(?:embed|v)\/([a-zA-Z0-9_-]+)|youtu\.be\/([a-zA-Z0-9_-]+)/);
446            if (videoIdMatch) {
447                iframeInfo.videoId = videoIdMatch[1] || videoIdMatch[2];
448            }
449
450            analysis.totalIframes++;
451            if (iframeInfo.needsUrlFix) analysis.needingUrlFix++;
452            if (iframeInfo.needsReferrerPolicy) analysis.needingReferrerPolicy++;
453            analysis.iframes.push(iframeInfo);
454        });
455
456        return analysis;
457    },
458
459    /**
460     * Initialize and set up observers for dynamic content
461     */
462    init: function () {
463        // Fix existing content
464        this.fixYouTubeContent();
465
466        // Set up MutationObserver to handle dynamically added content
467        const observer = new MutationObserver((mutations) => {
468            let shouldFix = false;
469
470            mutations.forEach(mutation => {
471                if (mutation.type === 'childList') {
472                    mutation.addedNodes.forEach(node => {
473                        if (node.nodeType === 1) { // Element node
474                            if (node.tagName === 'IFRAME' ||
475                                node.querySelector?.('iframe')) {
476                                shouldFix = true;
477                            }
478                        }
479                    });
480                }
481            });
482
483            if (shouldFix) {
484                // Debounce to avoid multiple rapid fixes
485                clearTimeout(this.fixTimeout);
486                this.fixTimeout = setTimeout(() => {
487                    this.fixYouTubeContent();
488                }, 100);
489            }
490        });
491
492        // Start observing
493        observer.observe(document.body, {
494            childList: true,
495            subtree: true
496        });
497
498        //console.log('YouTube Content Fixer initialized');
499    }
500};

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.