PageSourceSearch

https://smartketingsuite.ai/javascript/main.js

js smartketingsuite.ai collected 2026-10-05 15:05:51 UTC 97,155 bytes, 2,654 lines download raw bytes

1/* eslint-env jquery */
2function randomString(n = 12) {
3    const characters =
4        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
5    let result = ""
6    const charactersLength = characters.length
7
8    for (let i = 0; i < n; i++) {
9        result += characters.charAt(
10            Math.floor(Math.random() * charactersLength)
11        )
12    }
13
14    return result
15}
16
17function loadScript(src) {
18    if (document.querySelector(`script[src="${src}"]`)) return
19
20    const tag = document.createElement("script")
21    tag.async = true
22    tag.src = src
23    tag.defer = true
24    const firstScriptTag = document.getElementsByTagName("script")[0]
25    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag)
26}
27
28const STRIPE_JS_URL = "https://js.stripe.com/v3/"
29let stripeJsPromise = null
30
31// The server only ships Stripe.js on pages it detects as taking payments. If
32// that detection ever misses, load the library on demand instead of throwing
33// on `window.Stripe` halfway through a checkout.
34function ensureStripe() {
35    if (window.Stripe) return Promise.resolve()
36    if (stripeJsPromise) return stripeJsPromise
37
38    stripeJsPromise = new Promise((resolve, reject) => {
39        const tag = document.createElement("script")
40        tag.src = STRIPE_JS_URL
41        tag.onload = () => resolve()
42        tag.onerror = () => {
43            stripeJsPromise = null
44            reject(new Error("Failed to load Stripe.js"))
45        }
46        document.head.appendChild(tag)
47    })
48    return stripeJsPromise
49}
50
51function injectYoutubeScript() {
52    if (window.YT) return
53    loadScript("https://www.youtube.com/iframe_api")
54}
55
56function injectVimeoScript() {
57    if (window.Vimeo) return
58    loadScript("https://player.vimeo.com/api/player.js")
59}
60
61function injectWistiaScript() {
62    if (window._wq) return
63    loadScript("//fast.wistia.net/assets/external/E-v1.js")
64}
65
66function checkMEPlayerAvailable(callback) {
67    if (!window.MediaElementPlayer) {
68        setTimeout(() => {
69            checkMEPlayerAvailable(callback)
70        }, 50)
71    } else if (typeof callback === "function") {
72        callback()
73    }
74}
75
76const getYoutubeVideoId = (videoUrl) => {
77    if (!videoUrl) return null
78    const match = videoUrl.match(
79        /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
80    )
81    if (!match || match.length < 1) {
82        return null // Invalid YouTube URL
83    }
84
85    return match[1]
86}
87
88function getVimeoVideoId(url) {
89    if (!url) return null
90    const regex =
91        /(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(?:channels\/\w+\/|groups\/\w+\/videos\/|video\/|)(\d+)/
92    const match = url.match(regex)
93    return match ? match[1] : null
94}
95
96const getWistiaVideoId = (videoUrl) => {
97    if (!videoUrl) return null
98    const match = videoUrl.match(
99        /(?:https?:\/\/)?(?:www\.)?(?:wistia\.com|wi\.st)\/(?:medias|embed)\/([a-zA-Z0-9]+)/
100    )
101    if (!match || match.length < 1) {
102        return null // Invalid Wistia URL
103    }
104
105    return match[1]
106}
107
108const initializeYouTubePlayer = async (playerId, videoId, options = {}) => {
109    // Inject YouTube API script if not already present
110    if (!window.YT) {
111        injectYoutubeScript()
112    }
113
114    // Wait for the YouTube API to be available
115    const waitForYouTube = async () => {
116        while (!window.YT || !window.YT.Player) {
117            await new Promise((resolve) => setTimeout(resolve, 50)) // Retry every 50ms
118        }
119    }
120
121    await waitForYouTube() // Ensure YouTube API is loaded
122
123    // Create and return the YouTube Player instance
124    return new window.YT.Player(playerId, {
125        videoId,
126        playerVars: {
127            playlist: videoId,
128            rel: 0,
129            showInfo: 0,
130            playsInline: 1,
131            mute: 1,
132            autoplay: 1,
133            loop: 1,
134            disablekb: 1,
135            controls: 0,
136            iv_load_policy: 1,
137            cc_load_policy: 0,
138            ...options.playerVars // Allow overriding playerVars
139        },
140        events: options.events // Allow overriding events
141    })
142}
143
144const initializeVimeoPlayer = async (playerId, videoId, options = {}) => {
145    if (!window.Vimeo) {
146        injectVimeoScript()
147    }
148
149    // Wait for the Vimeo API to be available
150    const waitForVimeo = async () => {
151        while (!window.Vimeo) {
152            await new Promise((resolve) => setTimeout(resolve, 50)) // Retry every 50ms
153        }
154    }
155
156    await waitForVimeo() // Ensure Vimeo API is loaded
157
158    // Create and return the Vimeo Player instance
159    const playerOptions = {
160        id: parseInt(videoId),
161        autoplay: true,
162        muted: true,
163        background: true,
164        playsinline: true,
165        loop: true,
166        byline: false,
167        title: false,
168        portrait: false,
169        controls: false,
170        ...options // Allow overriding default options
171    }
172
173    return new window.Vimeo.Player(playerId, playerOptions)
174}
175
176const initializeWistiaPlayer = async (playerId, videoId, options = {}) => {
177    if (!window._wq) {
178        injectWistiaScript()
179    }
180
181    // Wait for the Wistia API to be available
182    const waitForWistia = async () => {
183        while (!window._wq) {
184            await new Promise((resolve) => setTimeout(resolve, 50)) // Retry every 50ms
185        }
186    }
187
188    await waitForWistia()
189
190    // Create the Wistia iframe and attach it to the specified player container
191    const iframe = document.createElement("iframe")
192    iframe.width = "100%"
193    iframe.height = "100%"
194    iframe.src = `https://fast.wistia.net/embed/iframe/${videoId}?autoplay=1&controls=0&loop=1&wmode=transparent&muted=1`
195    iframe.name = "wistia_embed"
196    iframe.frameBorder = "0"
197    document.getElementById(playerId).appendChild(iframe)
198
199    // Initialize the Wistia player using _wq
200    window._wq = window._wq || []
201    window._wq.push({
202        id: videoId,
203        onReady: function (video) {
204            if (options.onReady) options.onReady(video)
205        }
206    })
207}
208
209async function loadVideoBackground() {
210    const element = document.querySelector("[data-video-background]")
211    if (!element) return
212    let data = {}
213    try {
214        data = JSON.parse(element.dataset.videoBackground)
215    } catch (error) {
216        console.warn("invalid video background settings.")
217        return
218    }
219    element.removeAttribute("data-video-background")
220    if (!data?.video || data?.enableVideo !== "Yes") return
221    let videoId = null
222
223    if (data.videoType === "youtube") {
224        videoId = getYoutubeVideoId(data.video)
225    } else if (data.videoType === "vimeo") {
226        videoId = getVimeoVideoId(data.video)
227    } else if (data.videoType === "wistia") {
228        videoId = getWistiaVideoId(data.video)
229    }
230
231    if (!videoId) return
232    const playerId = `player_${randomString(10)}`
233    const containerId = `player_${randomString(10)}`
234    const ratio = 1.7777777778 // 16:9
235    const style = document.createElement("style")
236    style.innerHTML = `
237    #${containerId} {
238        position: fixed;
239        top: 0;
240        left: 0;
241        bottom: 0;
242        right: 0;
243        width: 100%;
244        height: 100%;
245        pointer-events: none;
246        z-index: -1;
247        overflow: hidden;
248    }
249    #${playerId} {
250        top: 50%;
251        left: 50%;
252        transform: translateX(-50%) translateY(-50%);
253        position: absolute;
254        opacity: 0;
255    }
256    `
257    const player = document.createElement("div")
258    player.id = playerId
259    element.id = containerId
260    element.appendChild(player)
261    element.appendChild(style)
262
263    if (data.videoType === "youtube") {
264        await initializeYouTubePlayer(playerId, videoId, {
265            events: {
266                onStateChange(evt) {
267                    if (evt.data === 1) {
268                        document.getElementById(playerId).style.opacity = 1
269                    } else {
270                        document.getElementById(playerId).style.opacity = 0
271                    }
272                }
273            }
274        })
275    } else if (data.videoType === "vimeo") {
276        const vimeoPlayer = await initializeVimeoPlayer(playerId, videoId)
277
278        vimeoPlayer.on("loaded", () => {
279            vimeoPlayer.play()
280            const playerElement = vimeoPlayer.element
281            playerElement.style.width = "100%"
282            playerElement.style.height = "100%"
283
284            document.getElementById(playerId).style.opacity = 1
285        })
286    } else if (data.videoType === "wistia") {
287        await initializeWistiaPlayer(playerId, videoId, {
288            onReady: (video) => {
289                document.getElementById(playerId).style.opacity = 1
290
291                video.bind("end", function () {
292                    video.play()
293                })
294            }
295        })
296    }
297
298    function resize() {
299        const height = window.innerHeight
300        const width = window.innerWidth
301        const cont = document.getElementById(playerId)
302        if (ratio > width / height) {
303            cont.style.width = `${height * ratio}px`
304            cont.style.height = `${height}px`
305        } else {
306            cont.style.width = `${width}px`
307            cont.style.height = `${width / ratio}px`
308        }
309    }
310    resize()
311    window.addEventListener("resize", resize)
312}
313
314function openPopup(node, data) {
315    const modalId = "elMediaPopupContainer"
316    document.getElementById(modalId)?.remove()
317
318    document.body.style.overflow = "hidden"
319    const modal = document.createElement("div")
320    const wrapper = document.createElement("div")
321    const backdrop = document.createElement("div")
322    const content = document.createElement("div")
323    const button = document.createElement("button")
324
325    const closePopup = () => {
326        const threeDSWrapper = modal.querySelector(".tds-wrapper")
327        if (threeDSWrapper) {
328            on3DSComplete(data)
329        }
330
331        modal.classList.remove("enter-popup")
332        modal.classList.add("exit-popup")
333
334        setTimeout(() => {
335            document.getElementById(modalId)?.remove()
336            document.body.style.overflow = ""
337        }, 300)
338    }
339
340    modal.id = modalId
341    modal.classList.add("enter-popup")
342    backdrop.classList.add("popup-backdrop")
343    backdrop.addEventListener("click", closePopup)
344    wrapper.classList.add("popup-content-wrapper")
345    content.classList.add("popup-content")
346    button.innerHTML = `
347        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="14" height="14" fill="currentColor">
348            <path d="M376.6 84.5c11.3-13.6 9.5-33.8-4.1-45.1s-33.8-9.5-45.1 4.1L192 206 56.6 43.5C45.3 29.9 25.1 28.1 11.5 39.4S-3.9 70.9 7.4 84.5L150.3 256 7.4 427.5c-11.3 13.6-9.5 33.8 4.1 45.1s33.8 9.5 45.1-4.1L192 306 327.4 468.5c11.3 13.6 31.5 15.4 45.1 4.1s15.4-31.5 4.1-45.1L233.7 256 376.6 84.5z"/>
349        </svg>`
350    button.classList.add("gjs-popup-close")
351    button.addEventListener("click", closePopup)
352
353    content.appendChild(node)
354    wrapper.appendChild(button)
355    wrapper.appendChild(content)
356    modal.appendChild(backdrop)
357    modal.appendChild(wrapper)
358
359    document.body.appendChild(modal)
360}
361
362function openImagePopup(url) {
363    const img = document.createElement("img")
364    img.src = url
365    openPopup(img)
366}
367
368function openVideoPopup(src, conf) {
369    const div = document.createElement("div")
370    const iframe = document.createElement("iframe")
371    div.classList.add("popup-iframe-scaler")
372
373    iframe.style.border = "none"
374    iframe.frameborder = "0"
375    iframe.title = conf?.videoTitle
376    iframe.allowFullscreen = true
377    iframe.classList.add("popup-iframe")
378    iframe.allow = "autoplay"
379    iframe.referrerPolicy = "strict-origin-when-cross-origin"
380    iframe.src = src
381    div.appendChild(iframe)
382
383    openPopup(div)
384}
385
386function open3DSPopup(src, data) {
387    const div = document.createElement("div")
388    const iframe = document.createElement("iframe")
389    div.classList.add("popup-iframe-scaler", "tds-wrapper")
390
391    iframe.style.border = "none"
392    iframe.frameborder = "0"
393    iframe.classList.add("popup-iframe")
394    iframe.width = 600
395    iframe.height = 400
396    iframe.referrerPolicy = "strict-origin-when-cross-origin"
397    iframe.src = src
398    div.appendChild(iframe)
399
400    openPopup(div, data)
401}
402
403async function on3DSComplete(data) {
404    try {
405        const iFrame = document.querySelector(".tds-wrapper")
406        iFrame.remove()
407
408        const url = new URL(
409            `https://api.stripe.com/v1/payment_intents/${data.paymentIntentId}?client_secret=${data.clientSecret}`
410        )
411        const retrieveRes = await fetch(url, {
412            method: "GET",
413            headers: {
414                "Content-Type": "application/x-www-form-urlencoded",
415                "Stripe-Account": data.accountId,
416                Authorization: `Bearer ${window.public_stripe_key}`
417            }
418        })
419        const retrieveData = await retrieveRes.json()
420        if (retrieveData.status === "succeeded") {
421            showToast("Payment was successful", {
422                hasCloseButton: true
423            })
424            if (data.exteral) {
425                window.location.href = data.exteral
426            } else {
427                try {
428                    await redirectNextPage()
429                } catch ({ error }) {
430                    showToast("Payment was not successful", {
431                        type: "error",
432                        hasCloseButton: true
433                    })
434                }
435            }
436        } else {
437            if (retrieveData && retrieveData.invoice) {
438                let invoice = null
439                if (typeof retrieveData.invoice === "string") {
440                    invoice = retrieveData.invoice
441                } else if (
442                    typeof retrieveData.invoice === "object" &&
443                    retrieveData.invoice.id
444                ) {
445                    invoice = retrieveData.invoice.id
446                }
447                if (invoice) {
448                    const url = new URL(
449                        `https://api.stripe.com/v1/invoices/${invoice}/void`
450                    )
451                    await fetch(url, {
452                        method: "GET",
453                        headers: {
454                            "Content-Type": "application/x-www-form-urlencoded",
455                            "Stripe-Account": data.accountId,
456                            Authorization: `Bearer ${window.public_stripe_key}`
457                        }
458                    })
459                }
460            }
461            // Authentication failed, prompt the customer to enter another payment method
462            showToast("Payment was not successful", {
463                type: "error",
464                hasCloseButton: true
465            })
466        }
467    } catch (err) {
468        showToast("Payment was not successful", {
469            type: "error",
470            hasCloseButton: true
471        })
472    }
473}
474
475function clickDataRef(url, conf) {
476    const videoId = getYoutubeVideoId(url)
477    const vimeoId = getVimeoVideoId(url)
478    if (videoId) {
479        const src = `//www.youtube.com/embed/${videoId}?autoplay=1&rel=0&hd=1&mute=1`
480        openVideoPopup(src, conf)
481    } else if (vimeoId) {
482        const src = `https://player.vimeo.com/video/${vimeoId}?autoplay=1&rel=0&muted=1`
483        openVideoPopup(src, conf)
484    } else {
485        // TODO: check URL is for Image, Video, Youtube, or others
486        openImagePopup(url)
487    }
488}
489
490function getAutoplayAttribute(autoplay) {
491    return autoplay === "yes" ? "allow='autoplay'" : ""
492}
493
494function getAutoplayParam(autoplay, blockPause, paramPrefix) {
495    return autoplay === "yes" || blockPause === "yes"
496        ? `${paramPrefix}autoplay=1`
497        : "autoplay=0"
498}
499
500function updateVideoWidth(element) {
501    const iframe = element.querySelector("iframe")
502    if (!iframe) return
503    const width = Number.isNaN(parseInt(iframe.width, 10))
504        ? element.clientWidth
505        : parseInt(iframe.width, 10)
506    const height = Number.isNaN(parseInt(iframe.height, 10))
507        ? width * 0.5625 // 16:9
508        : parseInt(iframe.height, 10)
509
510    element.style.paddingTop = `${(height / width) * 100}%`
511    element.classList.add("fluid-width-video-wrapper")
512}
513
514function handleVideoBlockPause(element, provider) {
515    const playerId = `player_${randomString(10)}`
516    const iframe = element.querySelector("iframe")
517    const unmuteLabel =
518        element.dataset.videoAutoplay === "yes" &&
519        element.dataset.videoUnmuteLabel === "yes" &&
520        element.dataset.videoControls === "no"
521    if (!iframe) return
522    iframe.id = playerId
523    if (element.dataset.videoBlockPause === "yes" || unmuteLabel) {
524        let player = null
525        const getPlayer = () => {
526            if (provider === "youtube") {
527                if (!window.YT) {
528                    setTimeout(getPlayer, 50)
529                } else {
530                    player = new window.YT.Player(playerId, {})
531                }
532            } else if (provider === "vimeo") {
533                if (!window.Vimeo) {
534                    setTimeout(getPlayer, 50)
535                } else {
536                    player = new window.Vimeo.Player(playerId)
537                }
538            } else if (provider === "wistia") {
539                if (!window._wq) {
540                    setTimeout(getPlayer, 50)
541                } else {
542                    window._wq.push({
543                        id: playerId,
544                        onReady(p) {
545                            player = p
546                        }
547                    })
548                }
549            }
550        }
551        setTimeout(getPlayer, 50)
552        const playVideoAndUnMute = () => {
553            if (!player) return
554            if (provider === "youtube") {
555                player.playVideo()
556                player.unMute()
557            } else if (provider === "vimeo") {
558                player.play()
559                player.setVolume(1)
560            } else if (provider === "wistia") {
561                player.play()
562                player.unmute()
563            }
564        }
565        const overlay = document.createElement("div")
566        overlay.classList.add("fluid-video-overlay")
567
568        if (element.dataset.videoBlockPause === "yes") {
569            const text =
570                element.dataset.videoStarterText ||
571                "Click To Join Session In progress..."
572            const blocker = document.createElement("div")
573            blocker.classList.add("start-session-wrapper")
574            blocker.innerHTML = `<span>${text}</span>`
575            blocker.addEventListener("click", () => {
576                playVideoAndUnMute()
577                blocker.remove()
578            })
579            overlay.appendChild(blocker)
580        } else if (unmuteLabel) {
581            overlay.innerHTML = "<span><i class='fa fa-volume-up'></i></span>"
582            overlay.addEventListener("click", () => {
583                playVideoAndUnMute()
584                overlay.remove()
585            })
586        }
587        element.appendChild(overlay)
588    }
589    element.removeAttribute("data-video-block-pause")
590    element.removeAttribute("data-video-controls")
591    element.removeAttribute("data-video-unmute-label")
592    element.removeAttribute("data-video-autoplay")
593    element.removeAttribute("data-video-starter-text")
594}
595
596function loadCustomEmbedVideo(e) {
597    const div = document.createElement("div")
598    div.innerHTML = e.dataset.videoEmbed ?? e.dataset.evpUrl ?? ""
599    updateVideoWidth(div)
600    e.appendChild(div)
601    e.removeAttribute("data-video-embed")
602    e.removeAttribute("data-evp-url")
603}
604
605function loadYoutubeVideoFormURL(element) {
606    const {
607        youtubeUrl,
608        videoAutoplay = "no",
609        videoControls = "yes",
610        videoBlockPause = "no",
611        videoWidth = "640",
612        videoHeight = "360"
613    } = element.dataset
614    const videoId = getYoutubeVideoId(youtubeUrl)
615    const autoplayParam = getAutoplayParam(
616        videoAutoplay,
617        videoBlockPause,
618        "mute=1&"
619    )
620    const controls =
621        videoBlockPause == "yes" || videoControls === "no" ? "0" : "1"
622    const div = document.createElement("div")
623    div.innerHTML = `
624            <iframe
625               width="${videoWidth}"
626               height="${videoHeight}"
627               ${getAutoplayAttribute(videoAutoplay)}
628               referrerPolicy="strict-origin-when-cross-origin"
629               src="https://www.youtube.com/embed/${videoId}?${autoplayParam}&controls=${controls}&enablejsapi=1&showinfo=0&rel=0&hd=1&wmode=transparent"
630               frameborder="0" allowfullscreen wmode="opaque">
631           </iframe>
632       `
633    updateVideoWidth(div)
634    element.appendChild(div)
635
636    element.removeAttribute("data-youtube-url")
637    handleVideoBlockPause(element, "youtube")
638}
639
640function loadVimeoVideoFromURL(element) {
641    const {
642        vimeoUrl,
643        videoAutoplay = "no",
644        videoBlockPause = "no",
645        videoWidth = "640",
646        videoHeight = "360"
647    } = element.dataset
648    const vimeoId = getVimeoVideoId(vimeoUrl)
649    if (!vimeoId) return
650
651    const div = document.createElement("div")
652
653    const autoplayParam = getAutoplayParam(
654        videoAutoplay,
655        videoBlockPause,
656        "muted=1&"
657    )
658    div.innerHTML = `
659    <iframe width="${videoWidth}"
660            height="${videoHeight}"
661            ${getAutoplayAttribute(videoAutoplay)}
662            src="https://player.vimeo.com/video/${vimeoId}?${autoplayParam}&title=0&byline=0&wmode=transparent&autopause=0"
663            frameborder="0" allowfullscreen wmode="opaque"></iframe>`
664
665    updateVideoWidth(div)
666    element.appendChild(div)
667    element.removeAttribute("data-vimeo-url")
668    handleVideoBlockPause(element, "vimeo")
669}
670
671function loadWistiaVideoFromURL(element) {
672    const {
673        wistiaUrl = "",
674        videoAutoplay = "no",
675        videoBlockPause = "no",
676        videoWidth = "640",
677        videoHeight = "360"
678    } = element.dataset
679    const wistiaId = wistiaUrl.split("/").pop()
680    if (!wistiaId) return
681    const div = document.createElement("div")
682    const autoplayParam = getAutoplayParam(
683        videoAutoplay,
684        videoBlockPause,
685        "muted=true&"
686    )
687    div.innerHTML = `
688    <iframe width="${videoWidth}"
689            height="${videoHeight}"
690            ${getAutoplayAttribute(videoAutoplay)}
691            src="//fast.wistia.net/embed/iframe/${wistiaId}?${autoplayParam}&wmode=transparent"
692            allowtransparency="true" frameborder="0" scrolling="no"
693            class="wistia_embed" name="wistia_embed"
694            allowfullscreen mozallowfullscreen webkitallowfullscreen oallowfullscreen msallowfullscreen wmode="opaque"></iframe>`
695    updateVideoWidth(div)
696    element.appendChild(div)
697    element.removeAttribute("data-wistia-url")
698    handleVideoBlockPause(element, "wistia")
699}
700
701function loadHTML5Video(element) {
702    const { mp4Url, webmUrl, posterUrl } = element.dataset
703    if (!mp4Url || !webmUrl) return
704
705    function startHTML5Video() {
706        const div = document.createElement("div")
707        div.classList.add("fluid-width-video-wrapper")
708        div.style.paddingTop = "56.25%"
709        const playerId = `player_${randomString(10)}`
710        const posterAttribute = posterUrl ? ` poster="${posterUrl}" ` : ""
711
712        div.innerHTML = `
713        <video id="${playerId}" preload="auto" ${posterAttribute} style="position: absolute; top: 0px; left: 0px;">
714            <source src="${mp4Url}" type="video/mp4">
715            <source src="${webmUrl}" type="video/webm">
716            Your browser does not support HTML5 video.
717        </video>`
718
719        element.removeAttribute("data-mp4-url")
720        element.removeAttribute("data-webm-url")
721        element.removeAttribute("data-poster-url")
722        element.appendChild(div)
723
724        new window.MediaElementPlayer(playerId, {
725            stretching: "auto",
726            videoWidth: "100%",
727            videoHeight: "100%",
728            iconSprite: "/images/mejs-controls.svg",
729            features: [
730                "playpause",
731                "current",
732                "progress",
733                "duration",
734                "volume"
735            ],
736            style: "position: absolute; top: 0px; left: 0px;"
737        })
738    }
739
740    checkMEPlayerAvailable(startHTML5Video)
741}
742
743function loadAudio(element) {
744    const {
745        audioUrl = "",
746        audioControls = "yes",
747        audioAutoplay,
748        audioLoop
749    } = element.dataset
750    const playerId = `audio_player_${randomString(10)}`
751
752    element.innerHTML = `
753    <div class="fluid-width-video-wrapper" style="height:40px;">
754        <audio id=${playerId} src="${audioUrl}"></audio>
755    </div>
756    `
757    const audioAttributes = {
758        src: audioUrl,
759        controls: audioControls !== "no",
760        loop: audioLoop === "yes",
761        autoplay: audioAutoplay === "yes"
762    }
763
764    let features = ["playpause", "current", "progress", "duration", "volume"]
765    if (!audioAttributes.controls) {
766        features = ["progress", "volume"]
767    }
768
769    const playerOptions = {
770        audioWidth: "100%",
771        audioHeight: "100%",
772        enableAutosize: true,
773        enableProgressTooltip: false,
774        enableKeyboard: audioAttributes.controls,
775        features,
776        loop: audioAttributes.loop,
777        iconSprite: "/images/mejs-controls.svg",
778        success: audioAttributes.autoplay
779            ? (mediaElement) => mediaElement.play()
780            : undefined
781    }
782
783    element.removeAttribute("data-audio-url")
784    element.removeAttribute("data-audio-controls")
785    element.removeAttribute("data-audio-autoplay")
786    element.removeAttribute("data-audio-loop")
787    new window.MediaElementPlayer(playerId, playerOptions)
788}
789
790function showToast(
791    message,
792    { type = "success", duration = 4000, hasCloseButton = false }
793) {
794    const toast = document.createElement("div")
795
796    toast.innerHTML = `<div>${message}</div>`
797    toast.className = `toast ${type}`
798
799    if (hasCloseButton) {
800        const closeIcon = document.createElement("i")
801        closeIcon.className = "fa fa-close toastCloseIcon"
802
803        closeIcon.onclick = () => {
804            toast.classList.add("hideToast")
805            setTimeout(() => toast.remove(), 500)
806        }
807
808        toast.appendChild(closeIcon)
809    }
810
811    document.body.appendChild(toast)
812
813    if (!hasCloseButton) {
814        toast.timeoutId = setTimeout(() => {
815            toast.classList.add("hideToast")
816
817            if (toast.timeoutId) clearTimeout(toast.timeoutId)
818
819            setTimeout(() => toast.remove(), 500)
820        }, duration)
821    }
822}
823
824async function sendRequest(endpoint, options) {
825    const parsedUrl = new URL(window.location.href)
826    const apiUrl = `${parsedUrl.protocol}//${parsedUrl.host}`
827
828    const response = await fetch(`${apiUrl}/${endpoint}`, options)
829    const data = await response.json()
830    if (!response.ok) throw data
831    return data
832}
833
834async function redirectNextPage({ ocuSubmit = false } = {}) {
835    const url = window.location.href
836    const response = await sendRequest("funnels/nextPage", {
837        method: "POST",
838        headers: { "Content-Type": "application/json" },
839        body: JSON.stringify({ url })
840    })
841
842    if (ocuSubmit) {
843        const res = await fetch(response.contentUrl)
844        const content = await res.text()
845        if (!content?.includes("#yes-link")) {
846            clearCookie("paymentId")
847            clearCookie("customerId")
848        }
849    }
850
851    // Redirect the next page
852    window.location.href =
853        url.substring(0, url.lastIndexOf("/")) + "/" + (response.path || "")
854}
855
856async function isExistRedirectURL() {
857    const url = window.location.href
858    const response = await sendRequest("funnels/currentPage", {
859        method: "POST",
860        headers: { "Content-Type": "application/json" },
861        body: JSON.stringify({ url })
862    })
863    if (response && response.metadata && response.metadata.redirectURL) {
864        return response.metadata.redirectURL
865    }
866    return
867}
868
869// eslint-disable-next-line
870function onYouTubePlayerAPIReady() {
871    // enable video background
872    loadVideoBackground()
873    // load Youtube videos
874    for (const element of document.querySelectorAll("[data-youtube-url]")) {
875        loadYoutubeVideoFormURL(element)
876    }
877}
878
879function saveSessionId(type, sessionId) {
880    const now = new Date()
881    const expirationTime = now.getTime() + 30 * 24 * 60 * 60 * 1000
882    const sessionData = {
883        value: sessionId,
884        expiration: expirationTime
885    }
886
887    localStorage.setItem(type, JSON.stringify(sessionData))
888}
889
890function checkSessionValid(type) {
891    const data = localStorage.getItem(type)
892    if (!data) return null
893
894    const json = JSON.parse(data)
895    if (!json.value || !json.expiration) return null
896
897    const now = new Date()
898    if (now.getTime() > json.expiration) return null
899
900    return json.value
901}
902
903function getCookie(name) {
904    const value = `; ${document.cookie}`
905    const parts = value.split(`; ${name}=`)
906    if (parts.length === 2) {
907        return parts.pop().split(";").shift()
908    }
909    return null
910}
911
912function clearCookie(name) {
913    document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
914}
915
916document.addEventListener("DOMContentLoaded", function () {
917    // load youtube iframe_api if the video background enabled
918    const videoBackgroundElement = document.querySelector(
919        "[data-video-background]"
920    )
921
922    if (videoBackgroundElement) {
923        try {
924            const data = JSON.parse(
925                videoBackgroundElement.dataset.videoBackground
926            )
927            const videoType = data.videoType
928
929            if (videoType === "youtube") {
930                injectYoutubeScript()
931            } else {
932                loadVideoBackground()
933            }
934        } catch (error) {
935            console.warn("invalid video background settings.")
936            return
937        }
938    }
939
940    const videoBackgroundElements = document.querySelectorAll(
941        "[data-background-video-type]"
942    )
943
944    videoBackgroundElements?.forEach(async (element) => {
945        const type = element.dataset.backgroundVideoType
946
947        element.style.position = "relative"
948
949        const player = document.createElement("div")
950        const playerId = `player_${randomString(10)}`
951        player.id = playerId
952
953        player.style.position = "absolute"
954        player.style.top = "0"
955        player.style.left = "0"
956        player.style.bottom = "0"
957        player.style.right = "0"
958        player.style.width = "100%"
959        player.style.height = "100%"
960        player.style.pointerEvents = "none"
961
962        element.insertBefore(player, element.firstChild)
963
964        if (type === "youtube") {
965            const url = element.dataset.backgroundYoutubeUrl
966            const videoId = getYoutubeVideoId(url)
967
968            if (!videoId) return
969
970            await initializeYouTubePlayer(playerId, videoId)
971        } else if (type === "vimeo") {
972            const url = element.dataset.backgroundVimeoUrl
973            const videoId = getVimeoVideoId(url)
974
975            if (!videoId) return
976
977            const vimeoPlayer = await initializeVimeoPlayer(playerId, videoId)
978
979            vimeoPlayer.on("loaded", () => {
980                vimeoPlayer.play()
981                const playerElement = vimeoPlayer.element
982                playerElement.style.width = "100%"
983                playerElement.style.height = "100%"
984            })
985        } else if (type === "wistia") {
986            const url = element.dataset.backgroundWistiaUrl
987            const videoId = getWistiaVideoId(url)
988
989            await initializeWistiaPlayer(playerId, videoId, {
990                onReady: (video) => {
991                    video.bind("end", function () {
992                        video.play()
993                    })
994                }
995            })
996        }
997    })
998
999    const aos = window.AOS
1000
1001    if (aos) {
1002        /**
1003         * available options are:
1004         * https://github.com/michalsnik/aos?tab=readme-ov-file#1-initialize-aos
1005         */
1006        aos.init({})
1007        /**
1008         * in this way we can initiate AOS animation on page load
1009         * otherwise animation will be triggered on scroll page.
1010         */
1011    }
1012
1013    const loadElements = document.querySelectorAll("[data-animation='load']")
1014    for (const element of loadElements) {
1015        element.classList.add("aos-animate")
1016    }
1017
1018    // "data-ref" attribute: show a popup
1019    for (const element of document.querySelectorAll("[data-ref]")) {
1020        const reference = element.dataset.ref
1021        const videoTitle = element.dataset.videoTitle
1022        if (reference) {
1023            const clickEvent = () => clickDataRef(reference, { videoTitle })
1024
1025            if (
1026                element.classList.contains("image-popup-wrapper") ||
1027                element.classList.contains("video-popup-wrapper")
1028            ) {
1029                for (const img of element.querySelectorAll("img")) {
1030                    img.tabIndex = "0"
1031                    img.addEventListener("click", clickEvent)
1032                }
1033            } else {
1034                element.tabIndex = "0"
1035                element.addEventListener("click", clickEvent)
1036            }
1037        }
1038    }
1039
1040    // "data-video-type" attribute: play video
1041    for (const element of document.querySelectorAll("[data-video-type]")) {
1042        const videoType = element.dataset.videoType
1043        if (videoType === "embed" || videoType === "evp") {
1044            loadCustomEmbedVideo(element)
1045        } else if (videoType === "youtube") {
1046            injectYoutubeScript()
1047        } else if (videoType === "vimeo") {
1048            injectVimeoScript()
1049            loadVimeoVideoFromURL(element)
1050        } else if (videoType === "wistia") {
1051            injectWistiaScript()
1052            loadWistiaVideoFromURL(element)
1053        } else if (videoType === "html5") {
1054            loadHTML5Video(element)
1055        } else {
1056            console.log("Unsupported Video Type: ", videoType)
1057        }
1058        element.removeAttribute("data-video-type")
1059    }
1060
1061    // ------------- Accordions -------------
1062    const accordions = document.querySelectorAll(".accordion-block")
1063
1064    accordions?.forEach((acc) => {
1065        const header = acc.querySelector(".accordion-block-header")
1066        header.addEventListener("click", function () {
1067            acc.classList.toggle("open")
1068
1069            const body = acc.querySelector(".accordion-block-body")
1070            const icon = acc.querySelector(".fa")
1071
1072            if (body.style.maxHeight) {
1073                body.style.maxHeight = null
1074                icon.className = "fa fa-plus"
1075            } else {
1076                body.style.maxHeight = body.scrollHeight + "px"
1077                icon.className = "fa fa-minus"
1078            }
1079        })
1080    })
1081
1082    // "data-audio-url" attribute: play audio
1083    for (const element of document.querySelectorAll("[data-audio-url]")) {
1084        checkMEPlayerAvailable(() => loadAudio(element))
1085    }
1086
1087    // ------------- element & video sticky start -------------
1088    // sticky elements
1089    const stickyElement = []
1090    for (const element of document.querySelectorAll(".sticky-top")) {
1091        const containerId = `sticky-container-${randomString(10)}`
1092        const div = document.createElement("div")
1093        div.id = containerId
1094        div.style.height = `${element.clientHeight}px`
1095        element.parentNode.insertBefore(div, element)
1096        div.appendChild(element)
1097
1098        stickyElement.push(containerId)
1099    }
1100
1101    const stickyVideos = []
1102    for (const e of document.querySelectorAll("[data-video-sticky='yes']")) {
1103        let id = e.id
1104        if (!id) {
1105            id = randomString(12)
1106            e.id = id
1107        }
1108        const {
1109            videoStickyWidth,
1110            videoStickyPosition,
1111            videoStickyTheme,
1112            videoStickyClose
1113        } = e.dataset
1114        stickyVideos.push({
1115            videoStickyId: id,
1116            videoStickyWidth: videoStickyWidth ?? "250",
1117            videoStickyPosition: videoStickyPosition ?? "top-right",
1118            videoStickyTheme: videoStickyTheme ?? "da-sticky-video-theme1",
1119            videoStickyClose: videoStickyClose ?? "none"
1120        })
1121        e.removeAttribute("data-video-sticky")
1122        e.removeAttribute("data-video-sticky-width")
1123        e.removeAttribute("data-video-sticky-position")
1124        e.removeAttribute("data-video-sticky-theme")
1125        e.removeAttribute("data-video-sticky-close")
1126    }
1127
1128    function scrollHandler() {
1129        for (const data of stickyVideos) {
1130            const { videoStickyId } = data
1131            const element = document.getElementById(videoStickyId)
1132            if (element) {
1133                const rect = element.getBoundingClientRect()
1134                if (rect.bottom < 0) {
1135                    stickyVideo(element, data, "add")
1136                } else {
1137                    stickyVideo(element, data, "remove")
1138                }
1139            }
1140        }
1141
1142        for (const stickyId of stickyElement) {
1143            const container = document.getElementById(stickyId)
1144            const element = container?.querySelector(".sticky-top")
1145            if (element && container) {
1146                const rect = container.getBoundingClientRect()
1147                if (rect.top < 0) {
1148                    element.style.position = "fixed"
1149                } else {
1150                    element.style.position = ""
1151                }
1152            }
1153        }
1154    }
1155
1156    function stickyVideo(e, d, m) {
1157        const iframe = e.querySelector("iframe")
1158        if (!iframe) return
1159        const {
1160            videoStickyPosition,
1161            videoStickyTheme,
1162            videoStickyClose,
1163            videoStickyWidth
1164        } = d
1165        iframe.classList[m](
1166            "sticky-video",
1167            videoStickyTheme,
1168            videoStickyPosition,
1169            `sticky-video-${videoStickyWidth}`
1170        )
1171        for (const element of e.querySelectorAll(".sticky-video-close")) {
1172            element.remove()
1173        }
1174        if (videoStickyClose === "yes" && m === "add") {
1175            const btn = document.createElement("button")
1176            btn.innerHTML = "<i class='fa fa-times'></i>"
1177            btn.classList.add(
1178                "sticky-video-close",
1179                videoStickyTheme,
1180                videoStickyPosition,
1181                `sticky-video-${videoStickyWidth}`
1182            )
1183            btn.addEventListener("click", () => {
1184                stickyVideo(e, d, "remove")
1185                window.removeEventListener("scroll", scrollHandler)
1186            })
1187            e.appendChild(btn)
1188        }
1189    }
1190
1191    if (stickyVideos.length > 0 || stickyElement.length) {
1192        window.addEventListener("scroll", scrollHandler)
1193    }
1194    // ------------- video sticky end -------------
1195})
1196
1197if (window.$) {
1198    let shouldShowExitPopup = true
1199
1200    function showGlobalPopup() {
1201        $(".gjs-popup").show()
1202        $(".gjs-popup").addClass("enter-popup")
1203
1204        $(".gjs-popup-close, .gjs-popup-backdrop").on("click", function () {
1205            $(".gjs-popup").removeClass("enter-popup")
1206            $(".gjs-popup").addClass("exit-popup")
1207
1208            setTimeout(() => {
1209                $(".gjs-popup").hide()
1210                $(".gjs-popup").removeClass("exit-popup")
1211            }, 300)
1212        })
1213    }
1214
1215    $(document).on("mouseout", (event) => {
1216        const $exitPopupTrigger = $("[data-trigger-on-exit='bounce']")
1217
1218        if (
1219            !event.relatedTarget &&
1220            !event.toElement &&
1221            $exitPopupTrigger?.length &&
1222            shouldShowExitPopup
1223        ) {
1224            shouldShowExitPopup = false
1225            showGlobalPopup()
1226        }
1227    })
1228
1229    // ------------- phone number element -------------
1230    const phoneElement = $("[name='phone']")
1231
1232    if (phoneElement.length) {
1233        const countryCodeElement = $("[name='country_codes_phone']")
1234        const MIN_PHONE_DIGITS = 8
1235
1236        function formatPhoneNumber(value, countryCode) {
1237            try {
1238                const phoneNumber = new libphonenumber.parsePhoneNumber(
1239                    value,
1240                    countryCode || "US"
1241                )
1242
1243                return phoneNumber.formatNational()
1244            } catch (error) {
1245                console.error("Error formatting phone number:", error)
1246                return value
1247            }
1248        }
1249
1250        countryCodeElement.on("change", function () {
1251            const selectedValue = $(this).val()
1252
1253            phoneElement.val(
1254                formatPhoneNumber(phoneElement.val(), selectedValue)
1255            )
1256        })
1257
1258        phoneElement.on("input", function () {
1259            const inputValue = $(this).val()
1260            const countryCodeValue = countryCodeElement?.val()
1261            const digitsOnly = inputValue.replace(/\D/g, "")
1262
1263            if (digitsOnly?.length >= MIN_PHONE_DIGITS) {
1264                $(this).val(formatPhoneNumber(inputValue, countryCodeValue))
1265            }
1266        })
1267    }
1268
1269    // ------------- customJs elements -------------
1270    $("[data-custom-js]").each(function () {
1271        const customJs = $(this).attr("data-custom-js")
1272        const type = $(this).attr("data-displaytype")
1273        type === "postscribe"
1274            ? window?.postscribe(this, customJs)
1275            : $(this).html(customJs)
1276        $(this).removeAttr("data-custom-js")
1277    })
1278
1279    // ------------- countdown timers -----------
1280    $(function () {
1281        function onExpireTimers() {
1282            const action = $(this).attr("data-expire-type")
1283
1284            if (action === "showhide") {
1285                const hide = $(this).attr("data-hide-ids")
1286                const show = $(this).attr("data-show-ids")
1287                if (hide) {
1288                    $.each(hide.split(","), function () {
1289                        $(`#${this}`).hide()
1290                    })
1291                }
1292                if (show) {
1293                    $.each(show.split(","), function () {
1294                        $(`#${this}`).show()
1295                    })
1296                }
1297            } else if (action === "url") {
1298                const url = $(this).attr("data-url")
1299                if (url) window.location.href = url
1300            }
1301        }
1302
1303        function onTickUpdated() {
1304            const updateColor = (selector, attr) => {
1305                const color = $(this).attr(attr)
1306                if (color) $(this).find(selector).css("color", color)
1307            }
1308            updateColor(".countdown-amount", "data-color-time")
1309            updateColor(".countdown-period", "data-color-label")
1310        }
1311
1312        function updateExpireCookie(e, date, zone) {
1313            const cookie = window.Cookies
1314            const action = $(e).attr("data-revisit-action")
1315            const days = $(e).attr("data-expire-cookie")
1316            const id = $(e).attr("id")
1317
1318            if ($(e).attr("data-expire-type") === "showhide") {
1319                const show = $(e).attr("data-show-ids")
1320                if (show) {
1321                    $.each(show.split(","), function () {
1322                        $(`#${this}`).hide()
1323                    })
1324                }
1325            }
1326            if (action === "newdiv") {
1327                const show = $(e).attr("data-show-ids-extra")
1328                if (show) {
1329                    $.each(show.split(","), function () {
1330                        $(`#${this}`).hide()
1331                    })
1332                }
1333            }
1334            if (cookie) {
1335                if (action === "reset") {
1336                    cookie.remove(`da-eg-timer-${id}`)
1337                    cookie.remove(`da-eg-timer-nd-${id}`)
1338                }
1339                if (cookie.get(`da-eg-timer-${id}`)) {
1340                    // expire/don't run the countdown until re-visit for x days.
1341                    date = 0
1342                    zone = 0
1343                } else if (cookie.get(`da-eg-timer-nd-${id}`)) {
1344                    date = 0
1345                    zone = 0
1346                    $(e).hide()
1347
1348                    const hide = $(e).attr("data-hide-ids")
1349                    const show = $(e).attr("data-show-ids")
1350                    const showExtra = $(e).attr("data-show-ids-extra")
1351                    if (hide) {
1352                        $.each(hide.split(","), function () {
1353                            $(`#${this}`).hide()
1354                        })
1355                    }
1356                    if (show) {
1357                        $.each(show.split(","), function () {
1358                            $(`#${this}`).hide()
1359                        })
1360                    }
1361                    if (showExtra) {
1362                        $.each(showExtra.split(","), function () {
1363                            $(`#${this}`).show()
1364                        })
1365                    }
1366                }
1367
1368                if (action === "expirecookie") {
1369                    if (!cookie.get(`da-eg-timer-${id}`) && days) {
1370                        cookie.set(`da-eg-timer-${id}`, "x", {
1371                            expires: parseInt(days)
1372                        })
1373                    }
1374                    cookie.remove(`da-eg-timer-nd-${id}`)
1375                } else if (action === "newdiv") {
1376                    if (days && !cookie.get(`da-eg-timer-${id}`)) {
1377                        cookie.set(`da-eg-timer-nd-${id}`, "x", {
1378                            expires: parseInt(days)
1379                        })
1380                    }
1381                    cookie.remove(`da-eg-timer-${id}`)
1382                }
1383            }
1384            return [date, zone]
1385        }
1386
1387        // prettier-ignore
1388        const locals = {
1389            ar: {
1390                labels: ["سنوات", "أشهر", "أسابيع", "أيام", "ساعات", "دقائق", "ثواني"],
1391                labels1: ["سنة", "شهر", "أسبوع", "يوم", "ساعة", "دقيقة", "ثانية"],
1392                compactLabels: ["س", "ش", "أ", "ي"],
1393                whichLabels: null,
1394                digits: ["Ù ", "Ù¡", "Ù¢", "Ù£", "Ù¤", "Ù¥", "Ù¦", "Ù§", "Ù¨", "Ù©"],
1395                timeSeparator: ":",
1396                isRTL: true
1397            },
1398            de: {
1399                labels: ["Jahre", "Monate", "Wochen", "Tage", "Stunden", "Minuten", "Sekunden"],
1400                labels1: ["Jahr", "Monat", "Woche", "Tag", "Stunde", "Minute", "Sekunde"],
1401                compactLabels: ["J", "M", "W", "T"],
1402                whichLabels: null,
1403                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1404                timeSeparator: ":",
1405                isRTL: false
1406            },
1407            en: {
1408                labels: ["Years", "Months", "Weeks", "Days", "Hours", "Minutes", "Seconds"],
1409                labels1: ["Year", "Month", "Week", "Day", "Hour", "Minute", "Second"],
1410                compactLabels: ["y", "m", "w", "d"],
1411                whichLabels: null,
1412                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1413                timeSeparator: ":",
1414                isRTL: false
1415            },
1416            es: {
1417                labels: ["Años", "Meses", "Semanas", "Días", "Horas", "Minutos", "Segundos"],
1418                labels1: ["Año", "Mes", "Semana", "Día", "Hora", "Minuto", "Segundo"],
1419                compactLabels: ["a", "m", "s", "d"],
1420                whichLabels: null,
1421                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1422                timeSeparator: ":",
1423                isRTL: false
1424            },
1425            fr: {
1426                labels: ["Années", "Mois", "Semaines", "Jours", "Heures", "Minutes", "Secondes"],
1427                labels1: ["Année", "Mois", "Semaine", "Jour", "Heure", "Minute", "Seconde"],
1428                compactLabels: ["a", "m", "s", "j"],
1429                whichLabels: function (amount) {
1430                    return amount > 1 ? 0 : 1
1431                },
1432                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1433                timeSeparator: ":",
1434                isRTL: false
1435            },
1436            it: {
1437                labels: ["Anni", "Mesi", "Settimane", "Giorni", "Ore", "Minuti", "Secondi"],
1438                labels1: ["Anno", "Mese", "Settimana", "Giorno", "Ora", "Minuto", "Secondo"],
1439                compactLabels: ["a", "m", "s", "g"],
1440                whichLabels: null,
1441                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1442                timeSeparator: ":",
1443                isRTL: false
1444            },
1445            ja: {
1446                labels: ["年", "月", "週", "日", "時", "分", "秒"],
1447                labels1: ["年", "月", "週", "日", "時", "分", "秒"],
1448                compactLabels: ["年", "月", "週", "日"],
1449                whichLabels: null,
1450                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1451                timeSeparator: ":",
1452                isRTL: false
1453            },
1454            ko: {
1455                labels: ["년", "월", "주", "일", "시", "분", "초"],
1456                labels1: ["년", "월", "주", "일", "시", "분", "초"],
1457                compactLabels: ["년", "월", "주", "일"],
1458                compactLabels1: ["년", "월", "주", "일"],
1459                whichLabels: null,
1460                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1461                timeSeparator: ":",
1462                isRTL: false
1463            },
1464            ru: {
1465                labels: ["Лет", "Месяцев", "Недель", "Дней", "Часов", "Минут", "Секунд"],
1466                labels1: ["Год", "Месяц", "Неделя", "День", "Час", "Минута", "Секунда"],
1467                labels2: ["Года", "Месяца", "Недели", "Дня", "Часа", "Минуты", "Секунды"],
1468                compactLabels: ["л", "м", "н", "д"],
1469                compactLabels1: ["г", "м", "н", "д"],
1470                whichLabels: function (amount) {
1471                    var units = amount % 10
1472                    var tens = Math.floor((amount % 100) / 10)
1473                    return amount === 1
1474                        ? 1
1475                        : units >= 2 && units <= 4 && tens !== 1
1476                            ? 2
1477                            : units === 1 && tens !== 1
1478                                ? 1
1479                                : 0
1480                },
1481                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1482                timeSeparator: ":",
1483                isRTL: false
1484            },
1485            sv: {
1486                labels: ["År", "Månader", "Veckor", "Dagar", "Timmar", "Minuter", "Sekunder"],
1487                labels1: ["År", "Månad", "Vecka", "Dag", "Timme", "Minut", "Sekund"],
1488                compactLabels: ["Å", "M", "V", "D"],
1489                whichLabels: null,
1490                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1491                timeSeparator: ":",
1492                isRTL: false
1493            },
1494            "zh-CN": {
1495                labels: ["年", "月", "周", "天", "时", "分", "秒"],
1496                labels1: ["年", "月", "周", "天", "时", "分", "秒"],
1497                compactLabels: ["年", "月", "周", "天"],
1498                compactLabels1: ["年", "月", "周", "天"],
1499                whichLabels: null,
1500                digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
1501                timeSeparator: ":",
1502                isRTL: false
1503            }
1504        }
1505
1506        // ------------- minutes timer elements -----------
1507        $(".gjs-minute-timer-block").each(function () {
1508            const hours = $(this).attr("data-hours") ?? "0"
1509            const minutes = $(this).attr("data-minutes") ?? "0"
1510            const seconds = $(this).attr("data-seconds") ?? "0"
1511            const lang = $(this).attr("data-lang")
1512            let secs =
1513                parseInt(hours) * 3600 +
1514                parseInt(minutes) * 60 +
1515                parseInt(seconds)
1516
1517            const [countdownDate] = updateExpireCookie(this, secs, 0)
1518
1519            $(this).countdown(
1520                $.extend(
1521                    {
1522                        until: countdownDate,
1523                        padZeroes: true,
1524                        alwaysExpire: true,
1525                        onExpiry: onExpireTimers
1526                    },
1527                    locals[lang] ?? locals.en
1528                )
1529            )
1530        })
1531
1532        // ------------- daily timer elements -----------
1533        $(".gjs-day-timer-block").each(function () {
1534            // eslint-disable-next-line no-undef
1535            let date = moment().format("MM/DD/YYYY")
1536            date = date.split("/")
1537            const time = $(this).attr("data-time") ?? "00"
1538            const tz = $(this).attr("data-tz") ?? "America/New_York"
1539            const lang = $(this).attr("data-lang") ?? "en"
1540            date = new Date(date[2], date[0] - 1, date[1], time, 0, 0)
1541            // eslint-disable-next-line no-undef
1542            let zone = moment.tz(date, tz).utcOffset() / 60
1543
1544            const [countdownDate, timezone] = updateExpireCookie(
1545                this,
1546                date,
1547                zone
1548            )
1549
1550            $(this).countdown(
1551                $.extend(
1552                    {
1553                        until: countdownDate,
1554                        timezone: timezone,
1555                        padZeroes: true,
1556                        alwaysExpire: true,
1557                        onExpiry: onExpireTimers
1558                    },
1559                    locals[lang] ?? locals.en
1560                )
1561            )
1562        })
1563
1564        // ------------- countdown 2.0 elements -----------
1565        $(".gjs-countdown-block").each(function () {
1566            let date = $(this).attr("data-date")
1567            const time = $(this).attr("data-time") ?? "00"
1568            let tz = $(this).attr("data-tz") ?? "America/New_York"
1569            const lang = $(this).attr("data-lang") ?? "en"
1570            if (!date) return
1571            date = date.split("/")
1572
1573            date = new Date(date[2], date[0] - 1, date[1], time, 0, 0)
1574            // eslint-disable-next-line no-undef
1575            tz = moment.tz(date, tz).utcOffset() / 60
1576
1577            if ($(this).attr("data-expire-type") === "showhide") {
1578                const show = $(this).attr("data-show-ids")
1579                if (show) {
1580                    $.each(show.split(","), function () {
1581                        $(`#${this}`).hide()
1582                    })
1583                }
1584            }
1585
1586            let f = ""
1587            if ($(this).attr("data-show-years") === "true") f = `${f}y`
1588            if ($(this).attr("data-show-months") === "true") f = `${f}o`
1589            if ($(this).attr("data-show-weeks") === "true") f = `${f}w`
1590            if ($(this).attr("data-show-days") === "true") f = `${f}d`
1591            if ($(this).attr("data-show-hours") === "true") f = `${f}H`
1592            if ($(this).attr("data-show-minutes") === "true") f = `${f}M`
1593            if ($(this).attr("data-show-seconds") === "true") f = `${f}S`
1594
1595            $(this).countdown(
1596                $.extend(
1597                    {
1598                        format: f,
1599                        until: date,
1600                        timezone: tz,
1601                        padZeroes: true,
1602                        alwaysExpire: true,
1603                        onExpiry: onExpireTimers,
1604                        onTick: onTickUpdated
1605                    },
1606                    locals[lang] ?? locals.en
1607                )
1608            )
1609        })
1610    })
1611
1612    // ------------- form or page submission and data-url -----------
1613    $(function () {
1614        let isSubmitting = false
1615        let isDirty = false
1616
1617        async function submitData(data, returnUrl) {
1618            try {
1619                const url = window.location.href
1620                const globalRedirectUrl =
1621                    returnUrl || window.redirect_url_after_submit
1622
1623                data.url = url
1624                await sendRequest("contactsNew", {
1625                    method: "POST",
1626                    headers: { "Content-Type": "application/json" },
1627                    body: JSON.stringify(data)
1628                })
1629
1630                const redirectURL = await isExistRedirectURL()
1631                if (redirectURL) {
1632                    window.location.href = redirectURL
1633                }
1634
1635                if (!globalRedirectUrl) {
1636                    redirectNextPage()
1637                } else {
1638                    window.location.href = globalRedirectUrl
1639                }
1640            } catch (error) {
1641                console.error(error)
1642            }
1643        }
1644
1645        function redirectAfterSubmit() {
1646            if (window.redirect_url_after_submit) {
1647                window.location.href = window.redirect_url_after_submit
1648            }
1649        }
1650
1651        async function onSubmitPage(returnUrl) {
1652            if (isSubmitting) return
1653
1654            const values = {}
1655            let valid = true
1656            isDirty = true
1657
1658            $(".elInput:not(.elOrder2StepWrapper .elInput)").each(function () {
1659                const name = $(this).prop("name")
1660                const isCheckbox = $(this).attr("type") === "checkbox"
1661                const value = isCheckbox
1662                    ? $(this).is(":checked")
1663                    : $(this).val()
1664                const required = $(this).prop("required")
1665                if (required && !value) {
1666                    valid = false
1667                    if (isCheckbox) {
1668                        $(this).css("outline", "1px solid #b91517")
1669                        $(this).css("outline-offset", "-1px")
1670                    } else {
1671                        $(this).css("border-color", "#b91517")
1672                    }
1673                } else if (name && value !== "") {
1674                    values[name] = value
1675                }
1676
1677                if (name === "phone" && value?.length) {
1678                    try {
1679                        const parsedPhone = new libphonenumber.parsePhoneNumber(
1680                            value,
1681                            values?.country_codes_phone || "US"
1682                        )
1683
1684                        if (!parsedPhone.isPossible()) {
1685                            throw new Error("Invalid phone number")
1686                        }
1687                    } catch (error) {
1688                        $(this).css("border-color", "#b91517")
1689                        console.error("Error validating phone number:", error)
1690                    }
1691                }
1692            })
1693            let data = {}
1694            let addr = {}
1695            let shippingArr = {}
1696
1697            if ("address" in values) addr["addressFullName"] = values.address
1698            if ("city" in values) addr["addressCity"] = values.city
1699            if ("country" in values) addr["addressCountry"] = values.country
1700            if ("ca_province" in values)
1701                addr["addressState"] = values.ca_province
1702            if ("us_state" in values) addr["addressState"] = values.us_state
1703            if ("state" in values) addr["addressState"] = values.state
1704            if ("zip" in values) addr["addressZipCode"] = values.zip
1705
1706            if ("billing_address" in values)
1707                addr["addressFullName"] = values.billing_address
1708            if ("billing_city" in values)
1709                addr["addressCity"] = values.billing_city
1710            if ("billing_country" in values)
1711                addr["addressCountry"] = values.billing_country
1712            if ("billing_state" in values)
1713                addr["addressState"] = values.billing_state
1714            if ("billing_zip" in values)
1715                addr["addressZipCode"] = values.billing_zip
1716
1717            if ("shipping_address" in values)
1718                shippingArr["shippingAddressFullName"] = values.shipping_address
1719            if ("shipping_city" in values)
1720                shippingArr["shippingAddressCity"] = values.shipping_city
1721            if ("shipping_country" in values)
1722                shippingArr["shippingAddressCountry"] = values.shipping_country
1723            if ("shipping_state" in values)
1724                shippingArr["shippingAddressState"] = values.shipping_state
1725            if ("shipping_zip" in values)
1726                shippingArr["shippingAddressZipCode"] = values.shipping_zip
1727
1728            if ("first_name" in values || "last_name" in values) {
1729                data.name = `${values.first_name || ""} ${
1730                    values.last_name || ""
1731                }`
1732            }
1733            if ("name" in values) data.name = values.name
1734            if ("email" in values) data.email = values.email
1735            if ("phone" in values) {
1736                try {
1737                    const parsedPhone = new libphonenumber.parsePhoneNumber(
1738                        values.phone,
1739                        values?.country_codes_phone || "US"
1740                    )
1741                    if (!parsedPhone.isPossible()) {
1742                        throw new Error("Invalid phone number")
1743                    }
1744
1745                    data.phoneNumber = parsedPhone?.number
1746                } catch (error) {
1747                    console.error("Error parsing phone number:", error)
1748                    valid = false
1749                }
1750            }
1751            if ("sms_approved" in values)
1752                data.unsubscribedSMS = !values.sms_approved
1753            if (Object.keys(addr).length > 0) data.address = addr
1754            if (Object.keys(shippingArr).length > 0)
1755                data.shippingAddress = shippingArr
1756
1757            if (valid && "email" in data) {
1758                isSubmitting = true
1759                await submitData(data, returnUrl)
1760                isSubmitting = false
1761            } else {
1762                console.warn("invalid data received")
1763            }
1764        }
1765
1766        function toggleContent(content) {
1767            let hrefValue = content.startsWith("#") ? content.slice(1) : content
1768
1769            let showIds = []
1770            let hideIds = []
1771
1772            let parts = hrefValue.split(":")
1773
1774            parts.forEach((part) => {
1775                if (part.startsWith("show--")) {
1776                    showIds = part.replace("show--", "").split(",")
1777                } else if (part.startsWith("hide--")) {
1778                    hideIds = part.replace("hide--", "").split(",")
1779                }
1780            })
1781
1782            if (showIds.length) {
1783                $.each(showIds, function () {
1784                    $(`#${this}`).show()
1785                })
1786            }
1787
1788            if (hideIds.length) {
1789                $.each(hideIds, function () {
1790                    $(`#${this}`).hide()
1791                })
1792            }
1793        }
1794
1795        function scrollTo(href) {
1796            const scrollToId = href.split("-").pop()
1797
1798            if (!scrollToId) {
1799                throw new Error("Unable to extract ID from the href.")
1800            }
1801
1802            const targetElement = $(`#${scrollToId}`)
1803
1804            if (targetElement.length === 0) {
1805                throw new Error(`Element with ID "${scrollToId}" not found.`)
1806            }
1807
1808            $("html, body").animate(
1809                { scrollTop: targetElement.offset().top },
1810                { duration: 500, easing: "swing" }
1811            )
1812        }
1813
1814        async function ocuSubmitOrder(href) {
1815            if (isSubmitting) return
1816
1817            // No product setup
1818            if (href == "#") {
1819                redirectAfterSubmit()
1820                return
1821            }
1822
1823            if (!getCookie("paymentId") || !getCookie("customerId")) {
1824                redirectAfterSubmit()
1825                return
1826            }
1827
1828            const contactSession = checkSessionValid("contactSession")
1829            if (!contactSession) return
1830
1831            try {
1832                const contact = await sendRequest(
1833                    `contacts/session/${contactSession}`
1834                )
1835
1836                const payload = {
1837                    url: window.location.href,
1838                    contactId: contact._id,
1839                    priceId: href,
1840                    ocuSubmit: true
1841                }
1842
1843                const paymentResponse = await sendRequest("user/payment", {
1844                    method: "POST",
1845                    headers: { "Content-Type": "application/json" },
1846                    body: JSON.stringify(payload)
1847                })
1848
1849                if (paymentResponse.requiresAction) {
1850                    await ensureStripe()
1851                    const stripe = new window.Stripe(window.public_stripe_key, {
1852                        stripeAccount: paymentResponse.accountId
1853                    })
1854                    const { error: confirmError } =
1855                        await stripe.confirmCardPayment(
1856                            paymentResponse.clientSecret
1857                        )
1858                    if (confirmError) {
1859                        await sendRequest("user/cancel-and-decrease", {
1860                            method: "POST",
1861                            headers: {
1862                                "Content-Type": "application/json"
1863                            },
1864                            body: JSON.stringify(paymentResponse)
1865                        })
1866
1867                        showToast("Payment was not successful", {
1868                            type: "error",
1869                            hasCloseButton: true
1870                        })
1871                        throw new Error(confirmError.message)
1872                    }
1873                }
1874                const redirectURL = await isExistRedirectURL()
1875                if (redirectURL) {
1876                    window.location.href = redirectURL
1877                } else if (window.redirect_url_after_submit) {
1878                    window.location.href = window.redirect_url_after_submit
1879                } else {
1880                    try {
1881                        await redirectNextPage({ ocuSubmit: true })
1882                    } catch ({ error }) {
1883                        // prevent multiple payments
1884                        clearCookie("paymentId")
1885                        clearCookie("customerId")
1886                    }
1887                }
1888            } catch (error) {
1889                console.error(error)
1890                // declined.show()
1891            } finally {
1892                isSubmitting = false
1893            }
1894        }
1895
1896        $(".elSMS").each(function () {
1897            // hide SMS form on load the page
1898            $(this).find(".smsExtras").hide()
1899            $(this)
1900                .find(".smsCheckInput")
1901                .on("change", (evt) => {
1902                    if (evt.target.checked) {
1903                        $(this).find(".smsExtras").show()
1904                    } else {
1905                        $(this).find(".smsExtras").hide()
1906                    }
1907                })
1908
1909            $(this)
1910                .find("select")
1911                .on("change", () => {
1912                    const val = $(this).find("select").val()
1913                    $(this).find("input[name='phone']").val(`+${val} `)
1914                })
1915        })
1916
1917        $(".elButton, [data-url]").on("click", async function (evt) {
1918            const button = $(this)
1919            const action =
1920                button.attr("data-action-type") ?? button.attr("data-url")
1921            const target = button.attr("target") ?? button.attr("data-target")
1922            // used to redirect after form submission
1923            // if not exist, redriect to next step in the funnel
1924            const return_url = button.attr("data-return-url")
1925            const href = button.attr("href")
1926
1927            if (action === "#website-url#") return
1928
1929            evt.preventDefault()
1930            evt.stopPropagation()
1931
1932            if (action === "#open-popup") showGlobalPopup()
1933            else if (action === "#close-popup") console.log("close popup")
1934            else if (action === "#submit-form") onSubmitPage(return_url)
1935            else if (action === "#next-url#") redirectNextPage()
1936            else if (action === "#yes-link") {
1937                button.css("opacity", "0.5")
1938                button
1939                    .find(".button-main-text")
1940                    .append(
1941                        '<i class="fa fa-spinner fa-spin" style="margin-left: 4px;"></i>'
1942                    )
1943
1944                await ocuSubmitOrder(href)
1945
1946                button.find(".button-main-text i.fa-spinner").remove()
1947                button.css("opacity", "1")
1948            } else if (action === "#no-link") redirectAfterSubmit()
1949            else if (action === "#toggle-content#") toggleContent(href)
1950            else if (action === "#scroll-to#") scrollTo(href)
1951            else if (action && target === "_blank")
1952                window.open(action, "_blank")
1953            else if (action) window.location.href = action
1954            else console.warn("invalid data-url", action)
1955        })
1956
1957        $(".elInput:not(.elOrder2StepWrapper .elInput)").on(
1958            "input",
1959            function () {
1960                const isCheckbox = $(this).attr("type") === "checkbox"
1961                const value = isCheckbox
1962                    ? $(this).is(":checked")
1963                    : $(this).val()
1964                const name = $(this).prop("name")
1965                const required = $(this).prop("required")
1966
1967                if (name && name !== "sms_approved") {
1968                    if (isCheckbox) {
1969                        $(`.elInput[name="${name}"]`).prop("checked", value)
1970                        if (required && isDirty && !value) {
1971                            $(`.elInput[name="${name}"]`).css(
1972                                "outline",
1973                                "1px solid #b91517"
1974                            )
1975                            $(`.elInput[name="${name}"]`).css(
1976                                "outline-offset",
1977                                "-1px"
1978                            )
1979                        } else {
1980                            $(`.elInput[name="${name}"]`).css("outline", "")
1981                            $(`.elInput[name="${name}"]`).css(
1982                                "outline-offset",
1983                                ""
1984                            )
1985                        }
1986                    } else {
1987                        $(`.elInput[name="${name}"]`).val(value)
1988                        if (required && isDirty && !value) {
1989                            $(`.elInput[name="${name}"]`).css(
1990                                "border-color",
1991                                "#b91517"
1992                            )
1993                        } else {
1994                            $(`.elInput[name="${name}"]`).css(
1995                                "border-color",
1996                                ""
1997                            )
1998                        }
1999                    }
2000                }
2001            }
2002        )
2003    })
2004
2005    // ------------- 2 step order -----------
2006    $(function () {
2007        let products = []
2008        let selectedProductId = ""
2009        const formatCurrency = (value, currency) => {
2010            if (isNaN(+value)) {
2011                return value
2012            }
2013
2014            const formatter = new Intl.NumberFormat("en-US", {
2015                style: "currency",
2016                currency: currency
2017            })
2018
2019            return formatter.format(value / 100)
2020        }
2021
2022        $(".elCreditCardForm")
2023            .find(".elInput")
2024            .on("input", function (e) {
2025                const name = $(this).prop("name")
2026                let value = $(this).val()
2027                value = value.replace(/\D/g, "")
2028
2029                if (name === "cc_number") {
2030                    value = value.substring(0, 16)
2031                    $(this).val(value.replace(/(\d{4})(?=\d)/g, "$1 "))
2032                } else if (name === "cc_expiry") {
2033                    if (value.length === 1 && parseInt(value) > 1) {
2034                        value = "0" + value + "/"
2035                    } else if (value.length === 2) {
2036                        const month = parseInt(value)
2037                        if (month > 12 || month === 0) {
2038                            value = "12/"
2039                        } else {
2040                            value += "/"
2041                        }
2042                    } else if (value.length > 2) {
2043                        const currentYear = new Date()
2044                            .getFullYear()
2045                            .toString()
2046                            .slice(-2)
2047                        if (value.length > 4) {
2048                            value = value.substring(0, 4)
2049                        }
2050                        const monthPart = value.substring(0, 2)
2051                        let yearPart = value.substring(2)
2052                        if (yearPart.length === 2) {
2053                            if (parseInt(yearPart) < parseInt(currentYear)) {
2054                                yearPart = currentYear
2055                            }
2056                            value = monthPart + "/" + yearPart
2057                        } else if (yearPart.length === 1) {
2058                            if (
2059                                parseInt(yearPart) <
2060                                parseInt(currentYear.substring(0, 1))
2061                            ) {
2062                                yearPart = currentYear.substring(0, 1)
2063                            }
2064                            value = monthPart + "/" + yearPart
2065                        }
2066                    }
2067                    if (
2068                        e.originalEvent &&
2069                        e.originalEvent.inputType === "deleteContentBackward" &&
2070                        value.includes("/")
2071                    ) {
2072                        value = value.substring(0, 2)
2073                    }
2074                    $(this).val(value)
2075                } else if (name === "cc_cvc") {
2076                    if (value.length > 4) {
2077                        value = value.substring(0, 4)
2078                    }
2079                    $(this).val(value)
2080                }
2081            })
2082
2083        $(".elOrderSummaryWrapper").each(function () {
2084            $(this).find(".elOrderSummaryDetail").empty()
2085            $(this).find(".elProductLoadingIndicator").show()
2086        })
2087
2088        $(".elProductSelectWrapper").each(function () {
2089            $(this).find(".elProductRadioList").empty()
2090            $(this).find(".elProductDropdownWrapper select").empty()
2091            $(this).find(".elProductLoadingIndicator").show()
2092        })
2093
2094        if ($(".elProductSelectWrapper").length > 0) {
2095            const showNotFoundMessage = () => {
2096                $(".elProductSelectWrapper").each(function () {
2097                    $(this)
2098                        .find(".elProductLoadingIndicator")
2099                        .empty()
2100                        .append("<p>Products not found</p>")
2101                        .css({
2102                            "font-size": "14px",
2103                            "margin-top": "20px"
2104                        })
2105
2106                    $(".elOrder2StepWrapper")
2107                        .find(".elOrderCompleteButton")
2108                        .prop("disabled", true)
2109                        .css({ cursor: "not-allowed" })
2110                })
2111                $(".elOrderSummaryWrapper").remove()
2112            }
2113
2114            const appendProducts = () => {
2115                const options = products.map((p) => {
2116                    const _value = formatCurrency(p.unit_amount, p.currency)
2117                    const interval = p?.recurring?.interval
2118                        ? ` / ${p?.recurring?.interval}`
2119                        : ""
2120                    const valueStr = `${_value}${interval}`
2121                    return {
2122                        id: p.id,
2123                        name: p?.overriddenName ?? p.product.name,
2124                        amount: p?.overriddenPrice ?? valueStr,
2125                        images: p.product.images
2126                    }
2127                })
2128
2129                selectedProductId = options[0]?.id
2130
2131                const radioOptions = options
2132                    .map(
2133                        (p, i) => `
2134                        <label class="elProductRadioItem">
2135                            <div class="elProductName">
2136                                <input type="radio"
2137                                    name="product"
2138                                    value="${p.id}"
2139                                    ${i === 0 ? "checked" : ""}
2140                                ></input>
2141                                <span>${p.name}</span>
2142                            </div>
2143                            <div class="elProductPrice">${p.amount}</div>
2144                        </label>
2145                    `
2146                    )
2147                    .join("")
2148                const dropdownOptions = options
2149                    .map(
2150                        (p, i) => `
2151                        <option value="${p.id}" ${i === 0 ? "selected" : ""}>
2152                            ${p.name} - ${p.amount}
2153                        </option>
2154                        `
2155                    )
2156                    .join("")
2157
2158                const updateOrderSummary = (productId) => {
2159                    const op = options.find((p) => p.id === productId)
2160                    if (op && productId) {
2161                        $(".elOrderSummaryWrapper").each(function () {
2162                            $(this)
2163                                .find(".elOrderSummaryDetail")
2164                                .html(
2165                                    `<span>${op.name}</span><spanclass="elProductPrice">${op.amount}</span>`
2166                                )
2167                        })
2168                    }
2169                }
2170                $(".elProductSelectWrapper").each(function () {
2171                    $(this).find(".elProductLoadingIndicator").remove()
2172                    $(this)
2173                        .find(".elProductRadioList")
2174                        .append(radioOptions)
2175                        .find("input")
2176                        .on("input", function (evt) {
2177                            selectedProductId = evt.target.value
2178                            updateOrderSummary(selectedProductId)
2179                        })
2180                    $(this)
2181                        .find("select")
2182                        .append(dropdownOptions)
2183                        .on("input", function (evt) {
2184                            selectedProductId = evt.target.value
2185                            updateOrderSummary(selectedProductId)
2186                        })
2187                })
2188                updateOrderSummary(selectedProductId)
2189
2190                $(".elOrderSummaryWrapper").each(function () {
2191                    $(this).find(".elProductLoadingIndicator").remove()
2192                })
2193            }
2194
2195            $(async () => {
2196                try {
2197                    const url = encodeURIComponent(window.location.href)
2198                    const { result } = await sendRequest(
2199                        `pages/products?url=${url}`
2200                    )
2201                    if (!result.length) {
2202                        showNotFoundMessage()
2203                    } else {
2204                        products = [...result]
2205                        appendProducts()
2206                    }
2207                } catch (error) {
2208                    console.log(error)
2209                    showNotFoundMessage()
2210                }
2211            })
2212        }
2213
2214        $(".elInputFieldWrapper.hide").remove()
2215
2216        $(".elOrder2StepWrapper").each(function () {
2217            let isDirty = false
2218            let hasError = false
2219            let isSubmitting = false
2220            let data = null
2221            let stripe
2222            let cardElement
2223
2224            const stripeFieldLayout = $(".elCreditCardForm").attr(
2225                "data-stripe-field-layout"
2226            )
2227            const isOneField = stripeFieldLayout === "oneField"
2228
2229            // TODO: remove me once we enable shippind detail form
2230            $(".elShippingWrapper.hide").remove()
2231
2232            const goToNext = $(this).find(".elOrder2Step1 .elOrder2StepButton")
2233            const backToStep1 = $(this).find(".elOrder2GoBackStep")
2234            const completeOrder = $(this).find(".elOrderCompleteButton")
2235            const declined = $(this)
2236                .find(".elCreditCardForm .orderDeclinedMessage")
2237                .hide()
2238
2239            // Initialize Stripe after button elements are defined
2240            if (isOneField) {
2241                ensureStripe()
2242                    .then(() => {
2243                        stripe = new window.Stripe(window.public_stripe_key)
2244                        const elements = stripe.elements()
2245                        cardElement = elements.create("card", {
2246                            hidePostalCode: true,
2247                            disableLink: true
2248                        })
2249
2250                        let isMounted = false
2251                        cardElement.on("ready", () => {
2252                            isMounted = true
2253                        })
2254
2255                        // Only mount the card element when step 2 is shown
2256                        goToNext.on("click", () => {
2257                            const mountElement =
2258                                document.querySelector(".stripeCardElement")
2259                            if (mountElement && !isMounted) {
2260                                cardElement.mount(".stripeCardElement")
2261                            }
2262                        })
2263
2264                        // Unmount when going back to step 1
2265                        backToStep1.on("click", () => {
2266                            if (isMounted) {
2267                                cardElement.unmount()
2268                                isMounted = false
2269                            }
2270                        })
2271
2272                        // Add card validation listener
2273                        cardElement.on("change", (event) => {
2274                            if (event.error) {
2275                                declined.text(event.error.message).show()
2276                            } else {
2277                                declined.hide()
2278                            }
2279                        })
2280                    })
2281                    .catch((error) => {
2282                        console.error(error)
2283                        declined
2284                            .text(
2285                                "Payment form failed to load. Please refresh the page."
2286                            )
2287                            .show()
2288                    })
2289            }
2290
2291            function addError(input) {
2292                hasError = true
2293                $(input).css("border-color", "#b91517")
2294            }
2295
2296            function removeError(input) {
2297                $(input).css("border-color", "")
2298            }
2299
2300            function validateInput() {
2301                const name = $(this).prop("name")
2302                const value = $(this).val()
2303                const required = $(this).prop("required")
2304
2305                if (name === "name") {
2306                    if (value.length < 3) {
2307                        addError(this)
2308                    } else {
2309                        removeError(this)
2310                    }
2311                } else if (name === "email") {
2312                    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
2313                        addError(this)
2314                    } else {
2315                        removeError(this)
2316                    }
2317                } else if (name === "phone") {
2318                    try {
2319                        const parsedPhone = new libphonenumber.parsePhoneNumber(
2320                            value,
2321                            data?.country_codes_phone || "US"
2322                        )
2323                        if (!parsedPhone.isPossible()) {
2324                            addError(this)
2325                        } else {
2326                            removeError(this)
2327                        }
2328                    } catch (error) {
2329                        console.error("Error validating phone number:", error)
2330                        addError(this)
2331                    }
2332                } else if (
2333                    name === "shipping_address" ||
2334                    name === "shipping_city" ||
2335                    name === "shipping_state" ||
2336                    name === "shipping_zip" ||
2337                    name === "shipping_country"
2338                ) {
2339                    if (required && !value) {
2340                        addError(this)
2341                    } else {
2342                        removeError(this)
2343                    }
2344                } else if (name === "cc_number") {
2345                    const len = value.replace(/\s+/g, "").length
2346                    if (!(len >= 15 && len < 20)) {
2347                        addError(this)
2348                    } else {
2349                        removeError(this)
2350                    }
2351                } else if (name === "cc_expiry") {
2352                    if (!/^(0[1-9]|1[0-2])\/?([0-9]{2})$/.test(value)) {
2353                        addError(this)
2354                    } else {
2355                        const date = new Date()
2356                        const month = parseInt(value.substring(0, 2), 10)
2357                        const year = parseInt(value.substring(3), 10) + 2000
2358
2359                        const y = date.getFullYear()
2360                        const m = date.getMonth()
2361                        if (!(year > y || (year === y && month >= m))) {
2362                            addError(this)
2363                        } else {
2364                            removeError(this)
2365                        }
2366                    }
2367                } else if (name === "cc_cvc") {
2368                    if (/^\d{3,4}$/.test(value)) {
2369                        removeError(this)
2370                    } else {
2371                        addError(this)
2372                    }
2373                }
2374
2375                if (!data) data = {}
2376                data[name] = value
2377            }
2378
2379            const submitOrder = async () => {
2380                try {
2381                    isSubmitting = true
2382
2383                    const product = products.find(
2384                        (p) => p.id === selectedProductId
2385                    )
2386
2387                    const { _id: userContactId, sessionId: contactSessionId } =
2388                        await sendRequest("contacts", {
2389                            method: "POST",
2390                            headers: {
2391                                "Content-Type": "application/json"
2392                            },
2393                            body: JSON.stringify({
2394                                email: data.email,
2395                                name: data.name,
2396                                url: window.location.href
2397                            })
2398                        })
2399
2400                    saveSessionId("contactSession", contactSessionId)
2401
2402                    let paymentMethod = null
2403
2404                    if (isOneField) {
2405                        // Validate card is mounted
2406                        if (!cardElement) {
2407                            throw new Error(
2408                                "Stripe card element not initialized"
2409                            )
2410                        }
2411
2412                        // Check if card details are complete
2413                        if (!cardElement._complete) {
2414                            declined
2415                                .text("Please enter complete card details")
2416                                .show()
2417                            throw new Error("Incomplete card details")
2418                        }
2419
2420                        const result = await stripe.createPaymentMethod({
2421                            type: "card",
2422                            card: cardElement
2423                        })
2424
2425                        if (!result || !result.paymentMethod) {
2426                            declined
2427                                .text(
2428                                    "Failed to create payment method. Please check your card details."
2429                                )
2430                                .show()
2431                            throw new Error("Failed to create payment method")
2432                        }
2433
2434                        const accountPaymentMethod = await sendRequest(
2435                            "user/create-payment-method",
2436                            {
2437                                method: "POST",
2438                                headers: {
2439                                    "Content-Type": "application/json"
2440                                },
2441                                body: JSON.stringify({
2442                                    paymentMethodId: result.paymentMethod.id,
2443                                    accountId: product.accountId,
2444                                    name: data.name,
2445                                    email: data.email
2446                                })
2447                            }
2448                        )
2449
2450                        paymentMethod = accountPaymentMethod?.paymentMethod
2451                    } else {
2452                        const exp_month =
2453                            data.cc_expiry[0] === "0"
2454                                ? data.cc_expiry.substring(1, 2)
2455                                : data.cc_expiry.split("/")[0]
2456                        const exp_year = data.cc_expiry.split("/")[1]
2457
2458                        const response = await fetch(
2459                            "https://api.stripe.com/v1/payment_methods",
2460                            {
2461                                method: "POST",
2462                                headers: {
2463                                    "Content-Type":
2464                                        "application/x-www-form-urlencoded",
2465                                    "Stripe-Account": product.accountId,
2466                                    Authorization: `Bearer ${window.public_stripe_key}`
2467                                },
2468                                body: new URLSearchParams({
2469                                    type: "card",
2470                                    "card[number]": data.cc_number,
2471                                    "card[exp_month]": `${exp_month}`,
2472                                    "card[exp_year]": `20${exp_year}`,
2473                                    "card[cvc]": data.cc_cvc,
2474                                    "metadata[name]": data.name,
2475                                    "metadata[email]": data.email
2476                                })
2477                            }
2478                        )
2479                        if (!response.ok) {
2480                            throw new Error("failed connecting stripe!")
2481                        }
2482                        paymentMethod = await response.json()
2483                    }
2484
2485                    if (paymentMethod) {
2486                        const payload = {
2487                            url: window.location.href,
2488                            contactId: userContactId,
2489                            priceId: product.id,
2490                            paymentId: paymentMethod.id,
2491                            accountId: product.accountId,
2492                            type: product.type,
2493                            productName: product.product.name,
2494                            productFreeTrialDays: product?.freeTrialDays ?? 0
2495                        }
2496                        const paymentResponse = await sendRequest(
2497                            "user/payment",
2498                            {
2499                                method: "POST",
2500                                headers: { "Content-Type": "application/json" },
2501                                body: JSON.stringify(payload)
2502                            }
2503                        )
2504
2505                        await ensureStripe()
2506                        stripe = new window.Stripe(window.public_stripe_key, {
2507                            stripeAccount: product.accountId
2508                        })
2509
2510                        if (paymentResponse.requiresAction) {
2511                            const { error: confirmError } =
2512                                await stripe.confirmCardPayment(
2513                                    paymentResponse.clientSecret
2514                                )
2515                            if (confirmError) {
2516                                await sendRequest("user/cancel-and-decrease", {
2517                                    method: "POST",
2518                                    headers: {
2519                                        "Content-Type": "application/json"
2520                                    },
2521                                    body: JSON.stringify({
2522                                        ...paymentResponse,
2523                                        accountId: product.accountId
2524                                    })
2525                                })
2526
2527                                showToast("Payment was not successful", {
2528                                    type: "error",
2529                                    hasCloseButton: true
2530                                })
2531                                throw new Error(confirmError.message)
2532                            }
2533                            showToast("Payment was successful", {
2534                                hasCloseButton: true
2535                            })
2536                        }
2537
2538                        const redirectURL = await isExistRedirectURL()
2539                        if (redirectURL) {
2540                            window.location.href = redirectURL
2541                        }
2542
2543                        const exteral = $(this).attr("data-external-url")
2544                        if (exteral) {
2545                            window.location.href = exteral
2546                        } else {
2547                            try {
2548                                await redirectNextPage()
2549                            } catch ({ error }) {
2550                                showToast("Payment was successful", {
2551                                    hasCloseButton: true
2552                                })
2553                                console.error(error)
2554                            }
2555                        }
2556                    }
2557                } catch (error) {
2558                    console.error(error)
2559                    await sendRequest("contacts", {
2560                        method: "POST",
2561                        headers: {
2562                            "Content-Type": "application/json"
2563                        },
2564                        body: JSON.stringify({
2565                            email: data.email,
2566                            name: data.name,
2567                            url: window.location.href,
2568                            requestFrom: "purchase_form_step_1"
2569                        })
2570                    })
2571                    declined.show()
2572                } finally {
2573                    isSubmitting = false
2574                }
2575            }
2576
2577            $(this)
2578                .find(".elInput")
2579                .on("input", function () {
2580                    if (isDirty) validateInput.bind(this)()
2581                })
2582
2583            // next button!
2584            goToNext.on("click", async () => {
2585                isDirty = true
2586                hasError = false
2587
2588                $(this).find(".elOrder2Step1 .elInput").each(validateInput)
2589
2590                if (!hasError) {
2591                    isDirty = false
2592
2593                    // First send the contact request
2594                    const data = {
2595                        url: window.location.href,
2596                        requestFrom: "purchase_form_step_1"
2597                    }
2598
2599                    $(this)
2600                        .find(".elOrder2Step1 .elInput")
2601                        .each(function () {
2602                            const inputName = $(this).attr("name")
2603                            const inputValue = $(this).val()
2604
2605                            inputName === "phone"
2606                                ? (data.phoneNumber = inputValue)
2607                                : (data[inputName] = inputValue)
2608                        })
2609
2610                    sendRequest("contactsNew", {
2611                        method: "POST",
2612                        headers: { "Content-Type": "application/json" },
2613                        body: JSON.stringify(data)
2614                    }).then(() => {
2615                        console.log("contactsNew")
2616                    })
2617
2618                    // Then update classes to show step 2
2619                    $(this).removeClass("elOrderActiveStep1")
2620                    $(this).addClass("elOrderActiveStep2")
2621                }
2622            })
2623
2624            // back to shipping details form
2625            backToStep1.on("click", () => {
2626                isDirty = false
2627                $(this).removeClass("elOrderActiveStep2")
2628                $(this).addClass("elOrderActiveStep1")
2629            })
2630
2631            // submit orders
2632            completeOrder.on("click", async () => {
2633                isDirty = true
2634                hasError = false
2635                declined.hide()
2636                completeOrder.prop("disabled", true)
2637                completeOrder.children().hide()
2638                completeOrder.append('<i class="fa fa-spinner fa-spin"></i>')
2639
2640                if (!isOneField) {
2641                    $(this).find(".elOrder2Step2 .elInput").each(validateInput)
2642                }
2643
2644                if (!isSubmitting && selectedProductId && !hasError) {
2645                    await submitOrder()
2646                }
2647
2648                completeOrder.find("i.fa-spinner").remove()
2649                completeOrder.children().show()
2650                completeOrder.prop("disabled", false)
2651            })
2652        })
2653    })
2654}

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.