PageSourceSearch

https://book.saunahouse.com/js/blvd-booking-embed.js

js saunahouse.com collected 2026-09-26 01:20:20 UTC 28,488 bytes, 779 lines download raw bytes

1/*
2 * @file        blvd-booking-embed.js
3 * @author      Simpatico Design Studio
4 * @copyright   Copyright (C) 2023 Simpatico Design Studio
5 * @version     1.0.1
6 * @created     04-06-2023
7 *
8 * @description This script embeds the Simpatico + BLVD Booking App.
9 *
10 * Website:     https://simpaticodesignstudio.com
11 * Contact:     [email protected]
12 *
13 * Please contact Simpatico Design Studio for usage rights and more information.
14 *
15 *
16 * Querystring Variables:
17 * @url: customer's custom booking app domain
18 *
19 */
20let showModal;
21let preloadDiv;
22let isBookingAppMobile = isMobileDevice();
23let div_cart_count = ''
24
25function loadScript(src) {
26  return new Promise(function (resolve, reject) {
27    let script = document.createElement('script');
28    script.src = src;
29
30    script.onload = () => resolve(script);
31    script.onerror = () => reject(new Error(`Script load error for ${src}`));
32
33    document.head.append(script);
34  });
35}
36
37const dynamicLoadBlvdBookingEmbed = document.getElementById('blvd-booking-app-embed')?.getAttribute('data-dynamic') ?? false;
38if (dynamicLoadBlvdBookingEmbed === 'true') {
39  loadBlvdBookingEmbed();
40} else {
41  document.addEventListener('DOMContentLoaded', function () {
42    loadBlvdBookingEmbed();
43  });
44}
45
46function loadBlvdBookingEmbed() {
47  isBlvdEmbedLoaded = true;
48  const scheme = window.location.protocol === 'http:' ? 'http://' : 'https://';
49  let scriptTag = document.getElementById('blvd-booking-app-embed');
50  let dataUrl;
51  let dataTheme;
52  let dataTargetId;
53  let dataHOffset = "0px";
54  let dataHeight;
55  let showCloseButton = true;
56  let div_display = "none";
57  let div_position = 'fixed';
58  let div_top = "0";
59  let div_left = "0";
60  let div_right = "0";
61  let div_width = "525px";
62  let div_maxWidth = "100vw";
63  let div_height = "1024px";
64  let div_maxHeight = "100vh";
65  let div_zIndex = "9999999999";
66  let div_backgroundColor = '#cccccc';
67  let div_borderBottomRightRadius = '0px';
68  let div_overflow = 'hidden';
69  let div_targetID = '';
70  let div_backfrop_filter = 'blur(10px)';
71  let div_animate = '';;
72  let div_login_text = '';
73  let div_bgloading = '';
74  let b = getQueryStringValue("b") ?? null;
75  let o = getQueryStringValue("o") ?? null;
76  //console.log('b', b);
77
78
79  if (scriptTag) {
80    dataUrl = scriptTag.getAttribute('data-url');
81    dataTheme = scriptTag.getAttribute('data-theme') ?? '';
82    div_targetID = scriptTag.getAttribute('data-targetid') ?? div_targetID;
83    dataTop = scriptTag.getAttribute('data-top') ?? '0px';
84    dataHOffset = scriptTag.getAttribute('data-hoffset') ?? dataHOffset;
85    showCloseButton = scriptTag.getAttribute('data-close') ?? showCloseButton;
86    div_top = scriptTag.getAttribute('data-top') ?? div_top;
87    div_left = scriptTag.getAttribute('data-left') ?? div_left;
88    div_right = scriptTag.getAttribute('data-right') ?? div_right;
89    div_width = scriptTag.getAttribute('data-width') ?? div_width;
90    div_height = scriptTag.getAttribute('data-height') ?? div_height;
91    div_backgroundColor = scriptTag.getAttribute('data-bgcolor') ?? div_backgroundColor;
92    div_borderBottomRightRadius = scriptTag.getAttribute('data-radius') ?? div_borderBottomRightRadius;
93    div_overflow = scriptTag.getAttribute('data-overflow') ?? div_overflow;
94    div_backfrop_filter = scriptTag.getAttribute('data-blur') ?? div_backfrop_filter;
95    div_animate = scriptTag.getAttribute('data-animate') ?? '';
96    div_cart_count = scriptTag.getAttribute('data-cartcount') ?? div_cart_count;
97    div_login_text = scriptTag.getAttribute('data-logintext') ?? div_login_text;
98    div_bgloading = scriptTag.getAttribute('data-bgloading') ?? div_bgloading;
99
100    if (!dataUrl) {
101      let scriptSrc = new URL(scriptTag.src);
102      dataUrl = scriptSrc.hostname;
103    }
104    const cookieName = (dataUrl ? dataUrl.replace(/\./g, '_') + '_blvd_session' : '_blvd_session');
105
106    if (div_cart_count || div_login_text) {
107      updateDivsFromCookies(cookieName);
108    }
109    (function (cookieName) {
110      window.addEventListener('message', function (event) {
111        // Verify the origin of the message to ensure it's from the trusted iframe
112        if (event.origin === scheme + dataUrl) { // Replace with your iframe domain
113          if (event.data) {
114            if (event.data.cartCount !== undefined && div_cart_count) { // Check if div_cart_count is valid
115              document.querySelectorAll('.' + div_cart_count).forEach(function (element) {
116                element.innerText = event.data.cartCount;
117              });
118              setLoginTextCookie(cookieName + '_count', event.data.cartCount);
119              // console.log('Cart Count', event.data.cartCount);
120            }
121            div_login_text = scriptTag.getAttribute('data-logintext') ?? div_login_text;
122            if (event.data.loginText !== undefined && div_login_text) { // Check if div_login_text is valid
123              loginTextCookie = event.data.loginText;
124              loginTextCookie = loginTextCookie.replace(/\s+/g, '');
125              console.log('div_login_text', div_login_text);
126              document.getElementById(div_login_text).innerText = loginTextCookie;
127              setLoginTextCookie(cookieName + '_name', loginTextCookie);
128              // console.log('Login Text', loginTextCookie);
129            }
130          }
131        }
132      }, false);
133    })(cookieName);
134
135  } else {
136    console.error("Script tag with id 'blvd-booking-app-embed' not found.");
137  }
138  const blvd_u = getCookie('_blvd_u');
139
140  let eqs = "";
141  if (blvd_u) {
142    const blvd_obj = JSON.parse(blvd_u);
143    eqs = `&e=${blvd_obj.e}&f=${blvd_obj.f}&l=${blvd_obj.l}&p=${blvd_obj.p}`;
144  }
145
146  var iframeSrc = appendCampaignParams(scheme + dataUrl + "/" + "?sl=1" + eqs);
147
148  if (!isBookingAppMobile) {
149    // 1. Create a hidden DIV element with an ID of #blvd-booking-app
150    let bookNowDiv;
151    const existingBookNowDiv = document.getElementById('blvd-booking-app');
152
153    if (dataTheme === 'wide') {
154      //showCloseButton = false;
155      //div_display = "block";
156      div_top = dataTop //"unset";
157      //div_left = "unset";
158      //div_position = 'relative';
159      //div_width = "100vw";
160      div_maxWidth = "100%";
161      if (dataHOffset === "0") {
162        div_height = "100vh";
163        div_maxHeight = "100%";
164      } else {
165        div_height = "calc(100vh - " + dataHOffset + ")" //"100vh";
166        div_maxHeight = "unset";
167      }
168      div_backgroundColor = '#ffffff';
169      div_borderBottomRightRadius = '0px';
170      //div_overflow = 'auto';
171    }
172
173    if (existingBookNowDiv) {
174      console.log('existingBookNowDiv', existingBookNowDiv);
175      bookNowDiv = existingBookNowDiv;
176      //console.log('existing div');
177      const theme = bookNowDiv.getAttribute('data-theme');
178      if (theme === 'wide') {
179        showCloseButton = false;
180      }
181
182    } else {
183      bookNowDiv = document.createElement('div');
184      bookNowDiv.id = 'blvd-booking-app';
185      bookNowDiv.style.display = div_display;
186      bookNowDiv.style.position = div_position;
187      bookNowDiv.style.top = "0";
188      bookNowDiv.style.left = "0";
189      bookNowDiv.style.width = "100%";
190      bookNowDiv.style.maxWidth = "100vw";
191      bookNowDiv.style.height = "100%";
192      bookNowDiv.style.maxHeight = "100vh";
193      bookNowDiv.style.zIndex = div_zIndex;
194      bookNowDiv.style.backgroundColor = "rgba(0,0,0,0.5)";
195      bookNowDiv.style.overflow = div_overflow;
196      bookNowDiv.style.backdropFilter = div_backfrop_filter;
197
198      console.log('bookNowDiv', bookNowDiv);
199
200      if (dataTheme === 'wide') {
201        var targetDiv = document.getElementById(dataTargetId);
202        if (targetDiv) {
203          targetDiv.appendChild(bookNowDiv);
204        } else {
205          //console.error("Element with ID 'dataTargetId' not found.");
206          document.body.appendChild(bookNowDiv);
207        }
208      } else {
209        document.body.appendChild(bookNowDiv);
210      }
211      bookAppDiv = document.createElement('div');
212      bookAppDiv.id = 'blvd-booking-app-div';
213      bookAppDiv.style.position = div_position;
214      bookAppDiv.style.top = div_top;
215      bookAppDiv.style.left = div_left;
216      bookAppDiv.style.right = div_right;
217      bookAppDiv.style.width = div_width;
218      bookAppDiv.style.maxWidth = div_maxWidth;
219      bookAppDiv.style.height = div_height;
220      bookAppDiv.style.maxHeight = div_maxHeight;
221      bookAppDiv.style.backgroundColor = div_backgroundColor;
222      bookAppDiv.style.borderBottomRightRadius = div_borderBottomRightRadius;
223      bookAppDiv.style.overflow = div_overflow;
224      bookAppDiv.style.backdropFilter = div_backfrop_filter;
225      bookNowDiv.appendChild(bookAppDiv);
226
227      console.log('bookAppDiv', bookAppDiv);
228
229
230      if (div_bgloading) {
231        // Create the loading div
232        loadingDiv = document.createElement('div');
233        loadingDiv.style.backgroundImage = `url('${scheme}${dataUrl}/images/${div_bgloading}')`;
234        loadingDiv.style.backgroundRepeat = 'no-repeat';
235        loadingDiv.style.backgroundPosition = 'center';
236        loadingDiv.style.backgroundSize = 'contain'; // Ensure the entire image is visible
237        loadingDiv.style.width = '150px';
238        loadingDiv.style.height = '150px';
239        loadingDiv.style.margin = '30% auto 0 auto';
240        loadingDiv.innerHTML = ' ';
241
242        // Create a style element for the keyframes
243        const styleElement = document.createElement('style');
244        styleElement.textContent = `
245          @keyframes rotate {
246            from { transform: rotate(0deg); }
247            to { transform: rotate(360deg); }
248          }
249          .rotating-background {
250            animation: rotate 1s linear infinite;
251          }
252        `;
253
254        // Append the style element to the document head
255        document.head.appendChild(styleElement);
256
257        // Apply the rotating-background class to the loading div
258        loadingDiv.classList.add('rotating-background');
259
260        // Append the loading div to your container (e.g., bookAppDiv)
261        bookAppDiv.appendChild(loadingDiv);
262      }
263    }
264    // 2.a If the URL has a querystring o qs, change the iframe src
265    if (o) {
266      let decoded_o = decodeURIComponent(o);
267
268      if (decoded_o.includes('?')) {
269        const urlParts = decoded_o.split('?');
270        const queryParams = new URLSearchParams(urlParts[1]);
271        if (!queryParams.has('dl')) {
272          decoded_o += '&dl=1';
273        }
274      } else {
275        decoded_o += '?dl=1';
276      }
277
278      iframeSrc = appendCampaignParams(scheme + dataUrl + "/" + decoded_o);
279    }
280
281    // 2.b Loads the Simpatico BLVD Booking App in the DIV
282
283    let iframe;
284    const existingIframe = document.getElementById('blvd-iframe');
285
286    if (existingIframe) {
287      // If it exists, use the existing iframe
288      iframe = existingIframe;
289      iframe.src = iframeSrc;
290      console.log("existingIframe: ", existingIframe);
291    } else {
292      // If it doesn't exist, create a new iframe
293      //document.requestStorageAccess().then(() => {
294      iframe = document.createElement('iframe');
295      iframe.src = iframeSrc;
296      iframe.id = 'blvd-iframe';
297      iframe.allow = 'autofocus';
298      iframe.style.width = '100%';
299      iframe.style.height = '100%';
300      iframe.style.border = 'none';
301      iframe.style.position = 'absolute';
302      iframe.style.top = '0';
303      iframe.style.left = '0';
304      iframe.style.overflow = 'hidden';
305      iframe.frameborder = '0';
306      iframe.seamless = 'seamless';
307      iframe.sandbox = 'allow-top-navigation allow-scripts allow-same-origin allow-forms allow-modals';
308      iframe.withCredentials = true;
309      console.log("iframe: ", iframe);
310      bookAppDiv.appendChild(iframe);
311
312      console.log("bookAppDiv: ", bookAppDiv);
313      //});
314    }
315    // 3.a When a button with the class ".book-now" is clicked, the #blvd-booking-app DIV will become visible as a pop-up modal
316    showModal = () => {
317      bookNowDiv.style.display = 'block';
318      bookAppDiv.classList.add('animate-modal');
319    };
320
321    // 3.b If the URL has a querystring b=1, the app will open
322    if (b == "1") {
323      bookNowDiv.style.display = 'block';
324      bookAppDiv.classList.add('animate-modal');
325    }
326    console.log("iframeSrc: ", iframeSrc);
327
328    // Close the modal when clicking outside the iframe
329    bookNowDiv.addEventListener('click', (e) => {
330      if (e.target !== bookAppDiv) {
331        bookNowDiv.style.display = 'none';
332      }
333    });
334
335    // Create the close button element
336    if (showCloseButton) {
337      closeTop = scriptTag.getAttribute('data-closetop') ?? "1px";
338      closeRight = scriptTag.getAttribute('data-closeright') ?? "1px";
339      closeBG = scriptTag.getAttribute('data-closebg') ?? "#fff";
340      closeColor = scriptTag.getAttribute('data-closecolor') ?? "#000";
341      const closeButton = document.createElement('div');
342      closeButton.classList.add('close-button');
343      closeButton.innerHTML = '×';
344      closeButton.style.position = 'absolute';
345      closeButton.style.top = closeTop;
346      closeButton.style.right = closeRight;
347      closeButton.style.width = '30px';
348      closeButton.style.height = '30px';
349      closeButton.style.borderRadius = '50%';
350      closeButton.style.backgroundColor = closeBG;
351      closeButton.style.color = closeColor;
352      closeButton.style.border = '0.5px solid ' + closeColor;
353      closeButton.style.textAlign = 'center';
354      closeButton.style.lineHeight = '30px';
355      closeButton.style.cursor = 'pointer';
356      closeButton.style.fontSize = '20px';
357      closeButton.style.fontWeight = 'bold';
358
359      // Add event listener to close the modal when the close button is clicked
360      closeButton.addEventListener('click', () => {
361        bookNowDiv.style.display = 'none';
362        bookAppDiv.classList.remove('animate-modal');
363        document.body.style.overflow = 'auto';
364      });
365
366      // Add the close button to the bookAppDiv
367      bookAppDiv.appendChild(closeButton);
368    }
369
370    if (div_animate) {
371      const style = document.createElement('style');
372      const direction = ['slideUp', 'slideDown', 'slideLeft', 'slideRight'].includes(div_animate)
373        ? div_animate
374        : 'slideUp';
375      style.innerHTML = `
376        @keyframes slideUp {
377          from { transform: translateY(100%); }
378          to { transform: translateY(0); }
379        }
380        @keyframes slideDown {
381          from { transform: translateY(-100%); }
382          to { transform: translateY(0); }
383        }
384        @keyframes slideLeft {
385          from { transform: translateX(100%); }
386          to { transform: translateX(0); }
387        }
388        @keyframes slideRight {
389          from { transform: translateX(-100%); }
390          to { transform: translateX(0); }
391        }
392
393        #blvd-booking-app-div.animate-modal {
394          animation: ${direction} 0.3s ease-out;
395        }
396      `;
397      document.head.appendChild(style);
398    }
399
400  } else {
401    console.log('is mobile');
402    if (b == "1") {
403      console.log('is b')
404      let newWindowURL = scheme + dataUrl;
405
406      if (o) {
407        let decoded_o = decodeURIComponent(o);
408
409        if (decoded_o.includes('?')) {
410          const urlParts = decoded_o.split('?');
411          const queryParams = new URLSearchParams(urlParts[1]);
412          if (!queryParams.has('dl')) {
413            decoded_o += '&dl=1';
414          }
415        } else {
416          decoded_o += '?dl=1';
417        }
418
419        newWindowURL += '/' + decoded_o;
420      }
421
422      newWindowURL = appendCampaignParams(newWindowURL);
423
424      window.location.href = appendReturnUrl(newWindowURL);
425    }
426  }
427
428  //const bookNowButtons = document.querySelectorAll('.book-now');
429  const bookNowButtons = Array.from(document.querySelectorAll('.book-now')).filter(button => !button.closest('.no-book'));
430  bookNowButtons.forEach((button) => {
431    button.addEventListener('click', function (event) {
432      let link = checkIframeSrc(event.currentTarget.dataset.link) ?? '';
433      let deepLink = false;
434      if (link !== '') {
435        link = link.includes('?') ? `${link}&dl=1` : `${link}?dl=1`;
436        deepLink = true;
437      }
438      if (!isBookingAppMobile) {
439        let newIframeSrc = iframeSrc; // Default to the original iframeSrc
440
441        if (link) {
442          newIframeSrc = appendCampaignParams(checkIframeSrc(scheme + dataUrl + "/" + link));
443        }
444
445        const iframeElement = document.getElementById('blvd-iframe');
446        if (iframeElement) {
447          let shouldShowLoading = newIframeSrc.includes('?sl=1');
448          // Set the src only when opening the modal
449          if (!iframeElement.src) {
450            iframeElement.src = newIframeSrc;
451            shouldShowLoading = true;
452          } else if (iframeElement.src !== newIframeSrc) {
453            iframeElement.src = newIframeSrc;
454            if (deepLink) {
455              shouldShowLoading = true;
456            } else {
457              shouldShowLoading = false;
458            }
459          } else if (iframeElement.src === newIframeSrc) {
460            shouldShowLoading = false;
461          }
462
463          if (shouldShowLoading === true) {
464            try {
465              iframeElement.contentWindow.postMessage(
466                { showLoading: true },
467                scheme + dataUrl
468              );
469            } catch (error) {
470              // Silently fail on privacy-focused browsers like DuckDuckGo that block cross-origin messaging
471              console.debug('postMessage blocked:', error.message);
472            }
473          }
474
475        } else {
476          console.error('Iframe with id blvd-iframe not found');
477        }
478
479        showModal();
480      } else {
481        var newWindowURL = iframeSrc;
482        if (link !== '') {
483          newWindowURL = appendCampaignParams(scheme + dataUrl + '/' + link);
484        }
485        window.location.href = appendReturnUrl(newWindowURL);
486      }
487    });
488  });
489
490  preloadDiv = document.getElementById('blvd-preload');
491
492  if (preloadDiv) { createPreloadIframe(preloadDiv, scheme + dataUrl); }
493
494  document.body.addEventListener('change', function (e) {  // Note the async keyword here
495    if (e.target && e.target.id === 'preload_location') {
496      //console.log('change location');
497      const newOptionTags = updateSelectService(e.target.value);  // Now you can use await here
498      //console.log('newOptionTags', newOptionTags);
499
500      // Find #preload_service select box
501      const preloadServiceSelect = document.getElementById('preload_service');
502      if (preloadServiceSelect) {
503        // Empty the #preload_service select box
504        while (preloadServiceSelect.firstChild) {
505          preloadServiceSelect.removeChild(preloadServiceSelect.firstChild);
506        }
507        // Add the "Select" option
508        const selectOption = document.createElement('option');
509        selectOption.setAttribute('class', '');
510        selectOption.setAttribute('value', '');
511        selectOption.setAttribute('selected', '');
512        selectOption.textContent = 'Select';
513        preloadServiceSelect.appendChild(selectOption);
514
515        // Convert the returned options string into DOM nodes
516        const tempDiv = document.createElement('div');
517        tempDiv.innerHTML = newOptionTags;
518
519        // Append the new options into the #preload_service select box
520        Array.from(tempDiv.childNodes).forEach(function (node) {
521          if (node.tagName === 'OPTGROUP') {
522            preloadServiceSelect.appendChild(node.cloneNode(true)); // using cloneNode to ensure we don't move the node
523          }
524        });
525      }
526    }
527  });
528};
529
530function updateDivsFromCookies(cookieName) {
531  var cartCountCookie = getCookie(cookieName + '_count');
532  var loginTextCookie = getCookie(cookieName + '_name');
533
534  if (cartCountCookie) {
535    document.querySelectorAll('.' + div_cart_count).forEach(function (element) {
536      element.innerText = cartCountCookie;
537    });
538  }
539
540  if (loginTextCookie) {
541    scriptTag = document.getElementById('blvd-booking-app-embed');
542    div_login_text = scriptTag.getAttribute('data-logintext') ?? div_login_text;
543    document.getElementById(div_login_text).innerText = loginTextCookie;
544  }
545}
546
547function createPreloadIframe(preloadDiv, iframeSrc) {
548  return new Promise((resolve, reject) => {
549    // Search for a DIV where element id = "blvd-preload"
550    //console.log('preload div', preloadDiv);
551    // If that exists
552    if (preloadDiv) {
553      preloadDiv.style.width = '100%';
554      preloadDiv.style.height = '100%';
555      preloadDiv.style.border = 'none';
556      const datepicker_css = document.createElement('link');
557      datepicker_css.rel = 'stylesheet';
558      datepicker_css.href = 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css';
559      document.head.appendChild(datepicker_css);
560
561      const app_css = document.createElement('link');
562      app_css.rel = 'stylesheet';
563      app_css.href = iframeSrc + '/css/styles-preload-form.css';
564      document.head.appendChild(app_css);
565
566      loadScript('https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js')
567        .then(() => {
568          fetch(iframeSrc + '/preload')
569            .then(response => response.json())
570            .then(data => {
571              // Now data.html contains the HTML string generated by your PHP method
572              document.getElementById('blvd-preload').innerHTML = data.html;
573
574              // Initialize the datepicker after the HTML content has been added
575              jQuery(document).ready(function () {
576                var datepickerInitialized = false;
577
578                jQuery('.date').datepicker({
579                  format: 'mm/dd/yyyy'
580                }).on('show', function (e) {
581                  // The datepicker is now visible, so you can add your class
582                  jQuery('.datepicker').addClass('preload-cal');
583                  datepickerInitialized = true;
584                  // Trigger a resize event
585                  jQuery(window).trigger('resize');
586                });
587
588                // Add a click event handler to open the datepicker
589                jQuery('.date').on('click', function (e) {
590                  if (!datepickerInitialized) {
591                    jQuery(this).datepicker('show');
592                  }
593                });
594              });
595
596              // Add event listener to the button here
597              const preloadButton = document.getElementById('preload_button');
598              if (preloadButton) {
599                preloadButton.addEventListener('click', function (event) {
600                  // Prevent the default action
601                  event.preventDefault();
602
603                  // Get the values from the input fields
604                  const location_id = document.getElementById('preload_location').value;
605                  const service_id = document.getElementById('preload_service').value;
606                  const preload_date = document.getElementById('preload_date').value;
607
608                  // Get the iframe
609                  const iframe = document.getElementById('blvd-iframe');
610                  if (iframe) {
611                    // Change the src of the iframe
612                    iframe.src = appendCampaignParams(`${iframeSrc}/selectdatetime?loc=${location_id}&srv=${service_id}&dte=${preload_date}`);
613                    showModal();
614                  } else {
615                    console.error('Error: Could not find iframe with id "blvd-iframe".');
616                  }
617
618                  // Scroll to top of page
619                  window.scrollTo(0, 0);
620                });
621              } else {
622                console.error('Error: Could not find element with id "preload_button".');
623              }
624            })
625            .catch((error) => {
626              console.error('Error:', error);
627            });
628        }) // <--- This was missing in your original code
629    }
630  });
631}
632
633function updateSelectService(location_id) {
634  const params = new URLSearchParams({
635    'endpoint': 'createCart',
636    'client_id': '',
637    'location_id': location_id
638  }).toString();
639
640  return fetch(`${iframeSrc}/api/blvd-api.php?${params}`)
641    .then(response => response.json())
642    .then(data => {
643      let html = '';
644
645      data.availableCategories.forEach(category => {
646        let service_options = '';
647        category.availableItems.filter(service => !service.disabled && service.__typename === 'CartAvailableBookableItem')
648          .forEach(service => {
649            service_options += `<option value="${encodeURIComponent(service.id)}">${service.name}</option>`;
650          });
651        if (service_options !== "") {
652          html += `<optgroup label="${category.name}">`;
653          html += service_options;
654          html += `</optgroup>`;
655        }
656      });
657      //console.log('html', html);
658      return html;
659
660    })
661    .catch((error) => {
662      console.error('Error:', error);
663    });
664}
665
666function getCookie(name) {
667  var value = "; " + document.cookie;
668  var parts = value.split("; " + name + "=");
669  if (parts.length == 2) return parts.pop().split(";").shift();
670}
671
672function getQueryStringValue(key) {
673  const queryParams = new URLSearchParams(window.location.search);
674  return queryParams.get(key);
675}
676
677// Check if the cookies exist
678function cookiesEmbedExist(cookieName) {
679  return document.cookie.includes('XSRF-TOKEN') && document.cookie.includes(cookieName);
680}
681
682// Function to set cookies
683function setEmbedCookies(dataUrl, cookieName) {
684  // Check if cookies already exist
685  if (!cookiesEmbedExist(cookieName)) {
686    // Set session cookie
687    var now = new Date();
688    var time = now.getTime();
689    var expireTime = time + 1000 * 28800; // 8 hours in milliseconds
690    now.setTime(expireTime);
691
692    var cookieS = cookieName + "='';expires=" + now.toUTCString() + ";path=/; domain=" + dataUrl + "; SameSite=None; Secure;";
693    document.cookie = cookieS;
694
695    // Set XSRF-TOKEN cookie
696    var cookieX = "XSRF-TOKEN='';expires=" + now.toUTCString() + ";path=/; domain=" + dataUrl + "; SameSite=None; Secure";
697    document.cookie = cookieX;
698  }
699}
700
701function setLoginTextCookie(cookieName, cookieValue) {
702  // Get the domain from the current URL
703  var domainParts = window.location.hostname.split('.');
704  var domain = domainParts.slice(-2).join('.'); // Get the top-level domain and second-level domain
705
706  // Set session cookie
707  var now = new Date();
708  var time = now.getTime();
709  var expireTime = time + 1000 * 28800; // 8 hours in milliseconds
710  now.setTime(expireTime);
711
712  // Create the cookie string
713  var setCookie = cookieName + "=" + encodeURIComponent(cookieValue) + ";expires=" + now.toUTCString() + "; path=/; domain=" + domain + "; SameSite=None; Secure";
714  //console.log('setCookie', setCookie);
715  // Set the cookie
716  document.cookie = setCookie;
717}
718
719function isMobileDevice() {
720  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
721}
722
723function checkIframeSrc(link) {
724  // Check if Iframe Src contains querystrings "?"
725  if (typeof link !== 'undefined' && link !== null) {
726    if (link.indexOf('?') === -1) {
727      // If it does not have "?", add "?sl=1"
728      link += '?sl=1';
729    } else {
730      // If it has "?", check if it contains "sl=1"
731      const queryParams = new URLSearchParams(link.split('?')[1]);
732      if (!queryParams.has('sl')) {
733        // If "sl" is not present, add "sl=1" as the first query string
734        link = link.replace('?', '?sl=1&');
735      } else if (queryParams.get('sl') !== '1') {
736        // If "sl" is present but not equal to "1", update it
737        queryParams.set('sl', '1');
738        const baseUrl = link.split('?')[0];
739        link = baseUrl + '?' + queryParams.toString();
740      }
741    }
742  }
743  //console.log('link', link);
744  return link;
745}
746
747function getCampaignParams() {
748  const campaignParamKeys = [
749    'gad_source', 'gad_campaignid', 'gbraid', 'gclid',
750    'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_id', 'utm_term',
751    'fbclid'
752  ];
753
754  const currentParams = new URLSearchParams(window.location.search);
755  const campaignParams = new URLSearchParams();
756
757  campaignParamKeys.forEach(key => {
758    if (currentParams.has(key)) {
759      campaignParams.set(key, currentParams.get(key));
760    }
761  });
762
763  return campaignParams.toString();
764}
765
766function appendCampaignParams(url) {
767  const campaignParams = getCampaignParams();
768  if (!campaignParams) {
769    return url;
770  }
771
772  const separator = url.includes('?') ? '&' : '?';
773  return url + separator + campaignParams;
774}
775
776function appendReturnUrl(url) {
777  const separator = url.includes('?') ? '&' : '?';
778  return url + separator + 'embed_return_url=' + encodeURIComponent(window.location.href);
779}

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.