1 2<!DOCTYPE html> 3<html> 4<head> 5 <meta charset="utf-8" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=1.0" /> 7 <meta name="format-detection" content="telephone=no" /> 8 <meta property="og:locale" content="en_US" /> 9 <meta property="og:type" content="website" /> 10 <meta property="og:title" content="Maryâs Cleaning Care Inc - Urbandale, IA" /> 11 <meta property="og:url" content="https://maryscleaningcare.com/" /> 12 <meta property="og:image" content="https://du9m0k402rjmo.cloudfront.net/images/P_40420/24df8162-12a8-4f18-b575-057abacf3c0f.jpg" /> 13 <meta property="og:description" content="Maryâs Cleaning Care Inc has proudly served Urbandale, IA, and surrounding areas since 2009, providing dependable, high-quality residential cleaning services. We specialize in routine maintenance cleaning and one-time deep cleanings, tailored to meet the unique needs of each home. Our experienced team is committed to delivering thorough, consistent results that leave your home clean, comfortable, and inviting. We use eco-friendly cleaning products to support a healthy living environment for you and your family. When you choose Maryâs Cleaning Care Inc, you can count on reliable service and attention to detail every visit." /> 14 <meta name="description" content="Maryâs Cleaning Care Inc has proudly served Urbandale, IA, and surrounding areas since 2009, providing dependable, high-quality residential cleaning services. We specialize in routine maintenance cleaning and one-time deep cleanings, tailored to meet the unique needs of each home. Our experienced team is committed to delivering thorough, consistent results that leave your home clean, comfortable, and inviting. We use eco-friendly cleaning products to support a healthy living environment for you and your family. When you choose Maryâs Cleaning Care Inc, you can count on reliable service and attention to detail every visit." /> 15 <meta name="title" content="Maryâs Cleaning Care Inc - Urbandale IA" /> 16 <meta name="keywords" content="Carpet Cleaning,Cleaning,Cleaning and Maid Service,Cleaning Houses and Windows,Cleaning Service,Cleaning Service Near Me,Deep Clean,House Cleaning,Residential Cleaning,Salon Cleaning,Floor Waxing" /> 17 <title>Maryâs Cleaning Care Inc - Urbandale, IA</title> 18
18<script src="/Scripts/Profile/googleAnalytics.js" type="text/javascript"></script>
vendor: 112 bytes, lines 18-21
18 19 <script type="text/javascript" src="https://www.google.com/recaptcha/api.js" async defer></script> 20 21
21<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
21 22 23
23<script>window.jQuery || document.write('
23<script src="/scripts/templates/jquery-3.5.0.min.js"><\/script>')</script>
23 24 25
25<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
25 26 27
27<script>if (typeof (Popper) === 'undefined') { document.write('
27<script src="/scripts/templates/popper.min.js"><\/script>') }</script>
27 28 29 <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous"> 30 31 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> 32 33
33<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-mrcBkRTg0HmTfecoh1I+48Ptm7cAe18uN+3GsS5SXXGSwzpjIhJZkxEJ5jYaZmE" crossorigin="anonymous"></script>
33 34 35
35<script>if (typeof ($.fn.modal) === 'undefined') { document.write('
35<script src="/scripts/templates/bootstrap.min.js"><\/script>') }</script>
35 36 37
37<script src="/Scripts/Templates/googleMaps.js" type="text/javascript"></script>
37 38 <link href="/Content/Templates/custom-bootstrap.css" type="text/css" rel="stylesheet" /> 39
39<script src="/Scripts/bootbox.min.js" type="text/javascript"></script>
39 40
40<script src="/Scripts/Profile/jquery.barrating.1.1.4.js" type="text/javascript"></script>
40 41
41<script src="/Scripts/Templates/cleave.min.js" type="text/javascript"></script>
41 42
42<script src="/Scripts/Templates/cleave-phone.us.js" type="text/javascript"></script>
42 43
43<script src="/Scripts/Templates/slick.min.js" type="text/javascript"></script>
43 44 <link href="/Content/Templates/slick.css" type="text/css" rel="stylesheet" /> 45 <link href="/Content/Templates/slick-theme.css" type="text/css" rel="stylesheet" /> 46
46<script src="/Scripts/Templates/googleMapsMultiLocation.js" type="text/javascript"></script>
vendor: 66 bytes, lines 46-47
46 47 <script src="https://www.google.com/recaptcha/api.js?render=
476LcokacUAAAAAH_snk3O5bpdYwjpC5XAuXNLrJLH
vendor: 40 bytes, lines 47-48
47" type="text/javascript"></script> 48
48<script src="https://player.vimeo.com/api/player.js" type="text/javascript"></script>
48 49 50 <link href="/Content/Templates/chatbot.css" type="text/css" rel="stylesheet" /> 51 52 <meta name="viewport" content="width=device-width, user-scalable=yes,initial-scale=1, maximum-scale=2" /> 53 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> 54 55 56 <link href="/Content/Templates/platinum_fonts.css" type="text/css" rel="stylesheet" /> 57 58 <style> 59 :root { 60 --body_font: Open Sans; 61 --header_font: Roboto Slab; 62 --text_color: 0, 0, 0, 0.7; 63 --text_color_rgba: rgba(var(--text_color)); 64 --customer_color: #4DCCAA; 65 --slogan_font_size: 2.5rem; 66 --slogan_text_color: ; 67 --faq_question_text_color: #000000; 68 --post_text_color: 0, 0, 0, 0.7; 69 --post_text_color_rgba: rgba(var(--post_text_color)); 70 --text-transform: capitalize; 71 --scroll-padding-top: 0px; /* Initial value */ 72 } 73 </style> 74 <link href="/Content/Templates/platinum_1.css" type="text/css" rel="stylesheet" /> 75 76 <link rel="canonical" href="https://maryscleaningcare.com/"> 77 78
79<script type="application/ld+json"> 80{ 81 "@context": "https://schema.org", 82 "@graph": [ 83 { 84 "@type": "LocalBusiness", 85 "@id": "https://maryscleaningcare.com/#localbusiness", 86 "name": "Maryâs Cleaning Care Inc", 87 "url": "https://maryscleaningcare.com/", 88 "description": "Maryâs Cleaning Care Inc has proudly served Urbandale, IA, and surrounding areas since 2009, providing dependable, high-quality residential cleaning services. We specialize in routine maintenance cleaning and one-time deep cleanings, tailored to meet the unique needs of each home. Our experienced team is committed to delivering thorough, consistent results that leave your home clean, comfortable, and inviting. We use eco-friendly cleaning products to support a healthy living environment for you and your family. When you choose Maryâs Cleaning Care Inc, you can count on reliable service and attention to detail every visit.", 89 "image": "https://du9m0k402rjmo.cloudfront.net/images/P_40420/24df8162-12a8-4f18-b575-057abacf3c0f.jpg", 90 "logo": "https://du9m0k402rjmo.cloudfront.net/images/P_40420/24df8162-12a8-4f18-b575-057abacf3c0f.jpg", 91 "address": { 92 "@type": "PostalAddress", 93 "streetAddress": "7921 Douglas Ave", 94 "addressLocality": "Urbandale", 95 "addressRegion": "IA", 96 "postalCode": "50322", 97 "addressCountry": "US" 98 }, 99 "telephone": "(515) 270-1612", 100 "email": "[email protected]", 101 "geo": { 102 "@type": "GeoCoordinates", 103 "latitude": 41.62968, 104 "longitude": -93.727642 105 }, 106 "openingHoursSpecification": [ 107 { 108 "@type": "OpeningHoursSpecification", 109 "dayOfWeek": "Monday", 110 "opens": "09:00", 111 "closes": "16:00" 112 }, 113 { 114 "@type": "OpeningHoursSpecification", 115 "dayOfWeek": "Tuesday", 116 "opens": "09:00", 117 "closes": "16:00" 118 }, 119 { 120 "@type": "OpeningHoursSpecification", 121 "dayOfWeek": "Wednesday", 122 "opens": "09:00", 123 "closes": "16:00" 124 }, 125 { 126 "@type": "OpeningHoursSpecification", 127 "dayOfWeek": "Thursday", 128 "opens": "09:00", 129 "closes": "16:00" 130 }, 131 { 132 "@type": "OpeningHoursSpecification", 133 "dayOfWeek": "Friday", 134 "opens": "09:00", 135 "closes": "16:00" 136 } 137 ], 138 "paymentAccepted": "American Express, Personal Checks, Debit Cards, Discover, MasterCard, Visa", 139 "sameAs": [ 140 "https://www.facebook.com/MarysCleaningCare/", 141 "https://www.instagram.com/maryscleaningcare/" 142 ] 143 }, 144 { 145 "@type": "WebSite", 146 "@id": "https://maryscleaningcare.com/#website", 147 "url": "https://maryscleaningcare.com/", 148 "name": "Maryâs Cleaning Care Inc", 149 "description": "Maryâs Cleaning Care Inc has proudly served Urbandale, IA, and surrounding areas since 2009, providing dependable, high-quality residential cleaning services. We specialize in routine maintenance cleaning and one-time deep cleanings, tailored to meet the unique needs of each home. Our experienced team is committed to delivering thorough, consistent results that leave your home clean, comfortable, and inviting. We use eco-friendly cleaning products to support a healthy living environment for you and your family. When you choose Maryâs Cleaning Care Inc, you can count on reliable service and attention to detail every visit.", 150 "potentialAction": { 151 "@type": "SearchAction", 152 "target": "https://maryscleaningcare.com/?s={search_term_string}", 153 "query-input": "required name=search_term_string" 154 } 155 }, 156 { 157 "@type": "WebPage", 158 "@id": "https://maryscleaningcare.com/#webpage", 159 "url": "https://maryscleaningcare.com/", 160 "name": "Maryâs Cleaning Care Inc - Urbandale, IA", 161 "description": "Maryâs Cleaning Care Inc has proudly served Urbandale, IA, and surrounding areas since 2009, providing dependable, high-quality residential cleaning services. We specialize in rout
161ine maintenance cleaning and one-time deep cleanings, tailored to meet the unique needs of each home. Our experienced team is committed to delivering thorough, consistent results that leave your home clean, comfortable, and inviting. We use eco-friendly cleaning products to support a healthy living environment for you and your family. When you choose Maryâs Cleaning Care Inc, you can count on reliable service and attention to detail every visit.", 162 "isPartOf": { 163 "@id": "https://maryscleaningcare.com/#website" 164 }, 165 "about": { 166 "@id": "https://maryscleaningcare.com/#localbusiness" 167 }, 168 "primaryImageOfPage": { 169 "@type": "ImageObject", 170 "url": "https://du9m0k402rjmo.cloudfront.net/images/P_40420/24df8162-12a8-4f18-b575-057abacf3c0f.jpg" 171 }, 172 "breadcrumb": { 173 "@type": "BreadcrumbList", 174 "itemListElement": [ 175 { 176 "@type": "ListItem", 177 "position": 1, 178 "name": "Home", 179 "item": "https://maryscleaningcare.com/" 180 } 181 ] 182 } 183 }, 184 { 185 "@type": "Service", 186 "@id": "https://maryscleaningcare.com/#service", 187 "name": "Our Services", 188 "provider": { 189 "@id": "https://maryscleaningcare.com/#localbusiness" 190 }, 191 "serviceType": [ 192 "Commercial Cleaning", 193 "Janitorial Services", 194 "Residential Cleaning", 195 "Office Cleaning", 196 "Business Cleaning", 197 "Church Cleaning", 198 "Car Dealership Cleaning", 199 "Automotive Space Cleaning", 200 "Salon Cleaning", 201 "Carpet Cleaning", 202 "Tile Grout Cleaning", 203 "Floor Stripping", 204 "Floor Waxing", 205 "Deep Clean Service" 206 ], 207 "description": "Professional services offered by Maryâs Cleaning Care Inc" 208 }, 209 { 210 "@type": "Service", 211 "@id": "https://maryscleaningcare.com/#service1", 212 "name": "Residential Cleaning", 213 "provider": { 214 "@id": "https://maryscleaningcare.com/#localbusiness" 215 }, 216 "serviceType": "Residential Cleaning", 217 "description": "Your home deserves the best, and Maryâs Cleaning Care Inc delivers just that. We provide thorough and dependable residential cleaning services, from regular upkeep to detailed deep cleans. Our team tackles dust, grime, and clutter, leaving your home spotless and refreshed. Whether itâs a weekly clean or a one-time service for a special occasion, we use safe, eco-friendly products to protect your family and pets. Our attention to detail means every corner is cared forâso you can relax and enjoy your clean, comfortable space. Weâre here to make your home shine, inside and out." 218 }, 219 { 220 "@type": "Service", 221 "@id": "https://maryscleaningcare.com/#service2", 222 "name": "Commercial & Office Cleaning", 223 "provider": { 224 "@id": "https://maryscleaningcare.com/#localbusiness" 225 }, 226 "serviceType": "Commercial & Office Cleaning", 227 "description": "Maryâs Cleaning Care Inc helps businesses look their best with our reliable and professional commercial cleaning services. We service offices, churches, salons, dealerships, and more, offering flexible cleaning schedules and customized plans. Whether you need daily janitorial services or periodic deep cleaning, we ensure your space stays sanitized, organized, and inviting. From restrooms to break rooms and everything in between, we use high-quality products and pay close attention to detail. A clean work environment boosts morale and leaves a great impressionâlet us handle the cleaning so you can focus on running your business." 228 }, 229 { 230 "@type": "Service", 231 "@id": "https://maryscleaningcare.com/#service3", 232 "name": "Carpet & Floor Care", 233 "provider": { 234 "@id": "https://maryscleaningcare.com/#localbusiness" 235 }, 236 "serviceType": "Carpet & Floor Care", 237 "description": "Clean floors make a powerful difference in the look and feel of any space. Thatâs why Maryâs Cleaning Care Inc offers professional carpet cleaning, tile and grout care, and expert floor str
237ipping and waxing. Our team removes deep-down dirt, allergens, and stains from carpets while restoring shine to hard surfaces. We use commercial-grade equipment and safe solutions to bring your flooring back to life. Whether itâs your homeâs living room carpet or a high-traffic commercial lobby, our floor care services leave lasting results. Trust us to protect and enhance one of the most important parts of your spaceâyour floors." 238 }, 239 { 240 "@type": "FAQPage", 241 "@id": "https://maryscleaningcare.com/#faqpage", 242 "url": "https://maryscleaningcare.com/#faq", 243 "name": "Frequently Asked Questions", 244 "description": "Frequently asked questions about Maryâs Cleaning Care Inc", 245 "mainEntity": [ 246 { 247 "@type": "Question", 248 "name": "Where is Maryâs Cleaning Care located and how can I contact you?", 249 "acceptedAnswer": { 250 "@type": "Answer", 251 "text": "Maryâs Cleaning Care is based in Urbandale, IA, and surrounding communities with trusted, high-quality cleaning services. You can reach us at (515) 270-1612 or email [email protected] to schedule your cleaning service. We tailor our services to meet the needs of your business, whether itâs daily office cleaning, deep-cleaning high-traffic areas, or maintaining your floors with expert stripping and waxing" 252 } 253 }, 254 { 255 "@type": "Question", 256 "name": "What types of cleaning services do you offer?", 257 "acceptedAnswer": { 258 "@type": "Answer", 259 "text": "We specialize in a wide range of cleaning services, including residential cleaning, commercial cleaning, carpet cleaning, tile and grout care, and floor maintenance." 260 } 261 }, 262 { 263 "@type": "Question", 264 "name": "How do I book a cleaning appointment?", 265 "acceptedAnswer": { 266 "@type": "Answer", 267 "text": "Booking is simple! You can call us directly, email, or use our online booking system. We recommend scheduling at least 24 hours in advance to guarantee availability." 268 } 269 }, 270 { 271 "@type": "Question", 272 "name": "What is your cancellation policy?", 273 "acceptedAnswer": { 274 "@type": "Answer", 275 "text": "If you need to cancel or reschedule, please notify us at least 24 hours in advance to avoid any fees and help us adjust our schedule." 276 } 277 }, 278 { 279 "@type": "Question", 280 "name": "Do you use environmentally friendly cleaning products?", 281 "acceptedAnswer": { 282 "@type": "Answer", 283 "text": "Yes! Our team of trained and experienced professionals works with great attention to detail, using effective methods and environmentally friendly products to ensure every space we touch is left spotless and refreshed." 284 } 285 }, 286 { 287 "@type": "Question", 288 "name": "What areas does Maryâs Cleaning Care serve around Urbandale, IA?", 289 "acceptedAnswer": { 290 "@type": "Answer", 291 "text": "Maryâs Cleaning Care proudly serves Urbandale and surrounding communities including West Des Moines, Clive, Waukee, Johnston, Grimes, Ankeny, and the greater Des Moines metro. We provide both residential and commercial cleaning services across these areas with flexible scheduling." 292 } 293 }, 294 { 295 "@type": "Question", 296 "name": "Do you offer weekly or biweekly residential cleaning services?", 297 "acceptedAnswer": { 298 "@type": "Answer", 299 "text": "Yes! We specialize in recurring weekly and biweekly home cleanings. This includes dusting, vacuuming, mopping, kitchen cleaning, bathroom sanitizing, and general home upkeepâperfect for busy families or anyone looking to maintain a consistently clean home." 300 } 301 }, 302 { 303 "@type": "Question", 304 "name": "What types of commercial cleaning services do you provide?",
305 "acceptedAnswer": { 306 "@type": "Answer", 307 "text": "We offer customized commercial cleaning for offices, small businesses, retail spaces, and professional buildings. Services include disinfecting highâtouch areas, breakroom and restroom cleaning, floor care, trash removal, and afterâhours cleanings to minimize disruption." 308 } 309 }, 310 { 311 "@type": "Question", 312 "name": "Can you customize a cleaning plan for my home or business?", 313 "acceptedAnswer": { 314 "@type": "Answer", 315 "text": "Absolutely. Whether you need a detailed deep clean, routine residential cleanings, or a commercial cleaning plan tailored to your business hours, budget, and space type, we create customized solutions to fit your exact needs." 316 } 317 }, 318 { 319 "@type": "Question", 320 "name": "How do I get a quote for residential or commercial cleaning in the Urbandale area?", 321 "acceptedAnswer": { 322 "@type": "Answer", 323 "text": "You can request a free quote online or by phone. Weâll gather a few details about your home or business, your preferred cleaning frequency, and your location in or around Urbandale. Quotes are fast, transparent, and include no hidden fees." 324 } 325 } 326 ] 327 } 328 ] 329} 330</script>
330</head> 331<body> 332 <div id="bootstrapCssTest" class="d-none"></div> 333
333<script> 334 $(function () { 335 336 if ($('#bootstrapCssTest').is(':visible')) { 337 $("head").prepend('<link rel="stylesheet" href="/content/templates/bootstrap.min.css">'); 338 } 339 }); 340 </script>
340 341 342 343 344 345<input type="hidden" id="areaMarkers" /> 346<input type="hidden" id="selectedServiceId" value="" /> 347 348<!-- NAVBAR --> 349<div class="container-fullwidth fixed-top"> 350 351 352 353 354 355<header id="headernav" class="content-header custom-background-color d-flex flex-wrap align-items-center justify-content-between py-0 px-2"> 356 357 358 <div class="nav-phone-social pb-1 d-flex flex-wrap justify-content-center"> 359 <div class="my-1 header-address d-flex flex-wrap justify-content-center"> 360 <div class="nav-phone text-center nav-header-item-no-spacing mx-1"><a id="PhoneNav" class="text-font-color phone-nav-mobile" href="tel:5152701612"><i class="fas fa-phone fa-inverse fa-xs fa-rotate-90 mr-1"></i>(515) 270-1612</a></div> 361 <div class="nav-phone text-center nav-header-item-no-spacing mx-1 px-1"><a id="eMailNav" class="text-font-color phone-nav-mobile text-lowercase" href="mailto:[email protected]" target="_blank"><i class="fas fa-envelope fa-inverse fa-sm mr-1"></i>[email protected]</a></div> 362 </div> 363 </div> 364 365 366 <div class="nav-phone-social pb-1 d-flex flex-wrap justify-content-center"> 367 <div class="nav-social mx-1 px-1"> 368 <div class="mx-auto text-center d-flex flex-wrap justify-content-center"> 369 <div class="mt-1 mb-1 ml-2 mr-2"> 370 <a class="text-font-color social-link-footer" href="https://www.facebook.com/MarysCleaningCare/" target="_blank" id="facebook-social-footer" title="Facebook"> 371 <i class="fab fa-facebook-f fa-lg"></i> 372 </a> 373 </div> 374 <div class="mt-1 mb-1 ml-2 mr-2"> 375 <a class="text-font-color social-link-footer" href="https://www.instagram.com/maryscleaningcare/" target="_blank" id="instagram-social-footer" title="Instagram"> 376 <i class="fab fa-instagram fa-lg"></i> 377 </a> 378 </div> 379 </div> 380 </div> 381 </div> 382</header> 383 384 385<nav class="bg-default-color bg-light navbar navbar-light navbar-expand-xl pt-1"> 386 387 <div class="nav-image bg-default-color bg-light flex-column text-center nav-image-desktop"> 388 <a href="#home-section" class="navbar-brand ml-3" id="HomeNav" onclick="DisplayPage('#homePage')"> 389 <img src="https://du9m0k402rjmo.cloudfront.net/images/P_40420/24df8162-12a8-4f18-b575-057abacf3c0f.jpg" alt="navbar logo" class="d-line-block align-top nav-logo-medium" /> 390 </a> 391 </div> 392 393 <button class="navbar-toggler nav-menu collapsed" type="button" data-toggle="collapse" data-target="#navbarToggler" aria-controls="navbarToggler" aria-expanded="false" aria-label="Toggle navigation"> 394 <span class="navbar-toggler-icon"></span> 395 <span class="my-1 mx-2 close">X</span> 396 </button> 397 398 <div class="collapse navbar-collapse flex-column mx-auto" id="navbarToggler"> 399 400 <div class="d-none d-xl-block nav-section"> 401 <div class="navbar-nav d-flex flex-wrap align-items-center"> 402 403 404<div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="HomePageNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#homePage')">Home</a> 405</div><div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="AboutPageNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#aboutPage')">About</a> 406</div><div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="PhotosNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#imageryPage')">Gallery</a> 407</div><div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="ServicesPageNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#servicesPage')">Services</a> 408</div>
408<div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="testimonialsPageNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#testimonialsPage')">Testimonials</a> 409</div><div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="ContactPageNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#contactPage')">Contact</a> 410</div><div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="PostsPageNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#postsPage')">News</a> 411</div><div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a target="_blank" href="https://maryscleaningcare.com/optimamedia/products/P_40420/3bfc95da-7812-47f4-a8f6-4e2bb179a37b.jpg" id="DocumentPageNavigationLink" class="nav-link header-font p-3">More</a> 412</div><div class="col nav-item text-center nav-header-item mx-auto text-nowrap"> <a href="#" id="FaqPageNavigationLink" class="nav-link header-font p-3" onclick="DisplayPage('#faqPage')">FAQ</a> 413</div> </div> 414 </div> 415 416 <div class="d-xs-block d-xl-none"> 417 <ul class="navbar-nav mx-auto mr-auto mt-2 mt-xl-0 mb-2"> 418 419 420<li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="HomePageNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#homePage')">Home</a> 421</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="AboutPageNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#aboutPage')">About</a> 422</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="PhotosNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#imageryPage')">Gallery</a> 423</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="ServicesPageNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#servicesPage')">Services</a> 424</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="testimonialsPageNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#testimonialsPage')">Testimonials</a> 425</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="ContactPageNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#contactPage')">Contact</a> 426</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="PostsPageNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#postsPage')">News</a> 427</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a target="_blank" href="https://maryscleaningcare.com/optimamedia/products/P_40420/3bfc95da-7812-47f4-a8f6-4e2bb179a37b.jpg" id="DocumentPageNavigationLink" class="nav-link header-font pl-3">More</a> 428</li><li class="nav-header-item-no-spacing mr-1 ml-1 text-nowrap"> <a href="#" id="FaqPageNavigationLink" class="nav-link header-font pl-3" onclick="DisplayPage('#faqPage')">FAQ</a> 429</li> </ul> 430 </div> 431 </div> 432</nav> 433</div> 434 435<div id="pages" class="pages-left-medium"> 436 <!--Home Page Information--> 437 <div class="content collapse show bg-alt-color" id="homePage"> 438 439<div class="d-flex flex-wrap"> 440 441 <div class="w-100 mx-auto home-banner"> 442 <div id="homepagebanner" class="parallax w-100 aspect-ratio-16x9"> 443 <div class="img-fluid banner-image"><object data="https://fast.wistia.com/embed/medias/s679kpxzdx?fitStrategy=cover&endVideoBehavior=loop&controlsVisibleOnLoad=false&autoPlay=true&volumeControl=false&muted=true&playbar=false&smallPlayButton=false&settingsControl=false&control-bar-right=false" style="width: 100%; height: 100%;" onload="jQuery('.w-css-reset').hide(); jQuery('.w-bottom-bar').hide(); jQuery('.w-ui-container').remove(); "></object></div> 444 445 <div class="home-content"> 446 <div class="d-flex flex-wrap justify-content-center align-items-center px-2"></div> 447 </div> 448 </div> 449 </div> 450 451 <div class="w-100 w-lg-80 w-sm-90 w-md-90 mx-auto p-2"> 452 <div class="d-flex flex-row flex-wrap justify-content-center text-left py-2"> 453 <div class="col-md mx-auto text-center pt-2"> 454 <img alt="Image" src="https://du9m0k402rjmo.
454cloudfront.net/images/P_40420/225b441b-0005-46c6-aaec-7c9bb8981f5f.png" class="img-fluid mx-auto text-center" /> 455 </div> 456 <div class="col-md pt-2"> 457 <div class="mx-auto mb-2 section-information"> 458 <div class="strike"> 459 <span class="text-center header-item">About Us</span> 460 </div> 461 </div> 462 <div class="mx-auto mb-2 description-speakable" style="white-space: pre-line">Maryâs Cleaning Care Inc has proudly served Urbandale, IA, and surrounding areas since 2009, providing dependable, high-quality residential cleaning services. We specialize in routine maintenance cleaning and one-time deep cleanings, tailored to meet the unique needs of each home. Our experienced team is committed to delivering thorough, consistent results that leave your home clean, comfortable, and inviting. We use eco-friendly cleaning products to support a healthy living environment for you and your family. When you choose Maryâs Cleaning Care Inc, you can count on reliable service and attention to detail every visit.</div> 463 </div> 464 </div> 465 </div> 466 467 <div class="w-100 w-lg-80 w-sm-90 w-md-90 mx-auto p-2" id="photos-content"> 468 <div class="mx-auto mb-2 section-information"> 469 <div class="strike"> 470 <span class="text-center header-item">Highlights</span> 471 </div> 472 </div> 473 <div class="d-flex flex-row flex-wrap justify-content-center py-2"> 474 <div class="w-100 w-lg-30 w-md-30 w-sm-30 mx-auto"> 475 <div class="d-flex flex-wrap"> 476 <div class="col p-2"> 477<a href="#" id="photos-image-element1" class="thumbnail mx-auto d-block"> <img src="https://du9m0k402rjmo.cloudfront.net/images/P_40420/49d8a9ce-2aa1-402c-ab18-83e9a667ebc7.png" alt="" class="img-fluid d-block rounded mx-auto photo-image" /> 478 <div class="text-center pt-2" style="color:black">Residential Cleaning</div> 479 </a> </div> 480 </div> 481 </div> 482 <div class="w-100 w-lg-30 w-md-30 w-sm-30 mx-auto"> 483 <div class="d-flex flex-wrap"> 484 <div class="col p-2"> 485<a href="#" id="photos-image-element2" class="thumbnail mx-auto d-block"> <img src="https://du9m0k402rjmo.cloudfront.net/images/P_40420/3798b090-4728-4854-8a77-af72064675ad.png" alt="" class="img-fluid d-block rounded mx-auto photo-image" /> 486 <div class="text-center pt-2" style="color:black">Commercial & Office Cleaning</div> 487 </a> </div> 488 </div> 489 </div> 490 <div class="w-100 w-lg-30 w-md-30 w-sm-30 mx-auto"> 491 <div class="d-flex flex-wrap"> 492 <div class="col p-2"> 493<a href="#" id="photos-image-element3" class="thumbnail mx-auto d-block"> <img src="https://du9m0k402rjmo.cloudfront.net/images/P_40420/205df6ce-4756-4d76-b513-427bedfddefc.png" alt="" class="img-fluid d-block rounded mx-auto photo-image" /> 494 <div class="text-center pt-2" style="color:black">Carpet & Floor Care</div> 495 </a> </div> 496 </div> 497 </div> 498 </div> 499 </div> 500 501</div> 502 503 </div> 504 505 <!--About Page Information--> 506 <div class="content collapse bg-alt-color" id="aboutPage"></div> 507 508 <!--Services Page Information--> 509 <div class="content collapse bg-alt-color" id="servicesPage"></div> 510 511 <!--Testimonial Page Information--> 512 <div class="content collapse bg-alt-color" id="testimonialsPage"></div> 513
514 <!-- REVIEW MODAL --> 515 <div id="reviewFormParentContainer"></div> 516 517 <!--Contact Page Information--> 518 <div class="content collapse bg-alt-color" id="contactPage"></div> 519 520 <!--Imagery Page Information--> 521 <div class="content collapse bg-alt-color" id="imageryPage"></div> 522 523 <!--Posts Page Information--> 524 <div class="content collapse bg-alt-color" id="postsPage"> 525 526<div class="d-flex flex-wrap"> 527 528 529 <div class="w-100 mx-auto banner"> 530 <div class="parallax w-100"> 531 <div class="img-fluid banner-image " style="background-image: url(https://du9m0k402rjmo.cloudfront.net/images/P_40420/532f1553-3ab2-4bdf-8a87-be3ee400bcee.jpg)"></div> 532 </div> 533 </div> 534 <div class="w-100 w-lg-80 w-sm-90 w-md-90 mx-auto p-2"> 535 <div class="d-flex flex-row flex-wrap justify-content-center text-center"> 536 <div class="posts-partial-content w-100"> 537 <div class="mx-auto mb-2 section-information pt-3"> 538 <div class="strike"> 539 <span class="text-center header-item">News</span> 540 </div> 541 </div> 542 543 <div class="postsInfo" id="testimonialPostsInfo"></div> 544 <div class="text-center m-3"> 545 <button type="button" id="prevPosts" class="btn btn-light btn-md" disabled><i class="fas fa-angle-left"></i> Previous</button> 546 <button type="button" id="nextPosts" class="btn btn-light btn-md">Next <i class="fas fa-angle-right"></i></button> 547 </div> 548 </div> 549 </div> 550 </div> 551</div> 552 </div> 553 554 <!--Locations Page Information--> 555 <div class="content collapse bg-alt-color" id="locationsPage"></div> 556 557 <!--FAQ Page Information--> 558 <div class="content collapse bg-alt-color" id="faqPage"></div> 559 560 <!--Additional Content Page Information--> 561 <div class="content collapse bg-alt-color" id="contentPage"></div> 562 563</div> 564 565<!--FOOTER Information--> 566<footer id="footer-container" class="footer custom-background-color"> 567 568 569<div class="container-fluid pt-1"> 570 <div class="row pl-3 pr-3"> 571 <div id="footer-address" class="col-sm-3 col-md-4 col-lg-3 mx-auto d-flex flex-wrap footer-content"> 572 <div class="justify-content-left text-font-color"> 573 <div class="footer-header mb-2 mt-2 text-left text-nowrap">Contact Information</div> 574 <div class="content-large">Maryâs Cleaning Care Inc</div> 575 <div class="mt-1 content-medium"> 5767921 Douglas Ave </div> 577 <div class="content-medium">Urbandale, IA 50322</div> 578 <div class="mt-2 mb-2"> 579 <div class="mx-auto content-medium"> 580 <strong>Phone:</strong> <a id="footer_phoneLink" href="tel:5152701612" class="text-font-color footer-link">(515) 270-1612</a> 581 </div> 582 <div class="mx-auto content-medium"> 583 <strong>Mobile:</strong> <a id="footer_MobilePhoneLink" href="tel:5157241962" class="text-font-color contact-phone">(515) 724-1962</a> 584 </div> 585 <div class="mx-auto content-medium"> 586 <strong>Email:</strong> <a id="footer_ContactEmail" title="[email protected]" class="text-font-color break-word-hyphen" href="mailto:[email protected]">scheduling<wbr>@maryscleaningcare<wbr>.com</a> 587 </div> 588 </div> 589 </div> 590 </div> 591 <div id="footer-hours" class='col-sm-3 col-md-4 col-lg-3 mx-auto footer-content d-flex flex-wrap justify-content-center'> 592 <div class="justify-content-left text-font-color"> 593 <div class="footer-hours-detail business-info-footer"> 594 <div class="row"> 595 <div class="col-12 footer-header mt-2 mb-2 text-nowrap">Our Hours</div> 596 </div> 597 <div class="row"> 598 <div class="col-12 content-medium pt-1 pb-1">Holiday Hours May Vary. Please Call For Details.</div> 599 </div> 600 <div class="row content-medium pt-1 pb-1"> 601 <div class="col-6"> 602 <div class="mr-3 font-weight-bold"> 603 Monday 604 </div> 605 </div> 606 <div class="col-6"> 607 <div class="contact-hours ml-1 text-nowrap"> 608 9:00 AM - 4:00 PM 609 </div> 610 </div> 611 </div> 612 613 <div class="row content-medium pt-1 pb-1"> 614 <div class="col-6"> 615 <div class="mr-3 font-weight-bold"> 616 Tuesday 617 </div> 618 </div> 619 <div class="col-6"> 620 <div class="contact-hours ml-1 text-nowrap"> 621 9:00 AM - 4:00 PM 622 </div> 623 </div> 624 </div> 625 626 <div class="row content-medium pt-1 pb-1"> 627 <div class="col-6"> 628 <div class="mr-3 font-weight-bold"> 629 Wednesday 630 </div> 631 </div> 632 <div class="col-6"> 633 <div class="contact-hours ml-1 text-nowrap"> 634 9:00 AM - 4:00 PM 635 </div> 636 </div> 637 </div> 638 639 <div class="row content-medium pt-1 pb-1"> 640 <div class="col-6"> 641 <div class="mr-3 font-weight-bold"> 642 Thursday 643 </div> 644 </div> 645 <div class="col-6"> 646 <div class="contact-hours ml-1 text-nowrap"> 647 9:00 AM - 4:00 PM 648 </div> 649 </div> 650 </div> 651 652 <div class="row content-medium pt-1 pb-1"> 653 <div class="col-6"> 654 <div class="mr-3 font-weight-bold"> 655 Friday 656 </div> 657 </div> 658 <div class="col-6"> 659 <div class="contact-hours ml-1 text-nowrap"> 660 9:00 AM - 4:00 PM 661 </div> 662 </div> 663 </div> 664 665 666 </div> 667 </div> 668 </div> 669 <div id="footer-certificationandassociation" class="col-sm-3 col-md-4 col-lg-3 mx-auto footer-content d-flex flex-wrap"> 670 <div class="justify-content-left text-font-color">
671 <div id="associationsHeader" class="footer-header text-left mb-2 mt-2 text-nowrap">Associations</div> 672 <div id="associations">Urbandale Chamber of Commerce</div> 673 <div id="associations">Better Business Bureau (BBB)</div> 674 </div> 675 </div> 676 <div id="footer-sitenav" class='col-sm-3 col-md-4 col-lg-3 mx-auto footer-content d-flex flex-wrap justify-content-center'> 677 <div class="justify-content-left text-font-color"> 678 <div class="footer-header text-left mb-2 mt-2 text-nowrap">Site Navigation</div> 679 680 681<div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="HomePageNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#homePage')">Home</a> 682</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="AboutPageNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#aboutPage')">About</a> 683</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="PhotosNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#imageryPage')">Gallery</a> 684</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="ServicesPageNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#servicesPage')">Services</a> 685</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="testimonialsPageNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#testimonialsPage')">Testimonials</a> 686</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="ContactPageNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#contactPage')">Contact</a> 687</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="PostsPageNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#postsPage')">News</a> 688</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a target="_blank" href="https://maryscleaningcare.com/optimamedia/products/P_40420/3bfc95da-7812-47f4-a8f6-4e2bb179a37b.jpg" id="DocumentPageNavigationLink" class="text-font-color footer-link">More</a> 689</div><div class="text-left content-medium mx-auto text-nowrap pb-1"> <a href="#" id="FaqPageNavigationLink" class="text-font-color footer-link" onclick="DisplayPage('#faqPage')">FAQ</a> 690</div> <div class="text-left content-medium mx-auto text-nowrap"> 691 <a href="/Accessibility?domain=maryscleaningcare" target="_blank" class="text-font-color footer-link"> 692 Text-Only Site Version 693 </a> 694 </div> 695 </div> 696 </div> 697 </div> 698</div> 699 700<div class="d-flex flex-wrap text-center justify-content-center pb-3 pl-3 pr-3"> 701 <div class="d-flex text-center text-nowrap tos pt-1 w-100 justify-content-center"> 702 <a class="text-font-color" href="/Home/TermsOfUse" id="tosLink" target="_blank">Terms of Use</a><div id="footerdash"> | </div><a class="text-font-color" href="/Home/PrivacyPolicy" id="ppLink" target="_blank">Privacy Policy</a> 703 </div> 704 <div class="d-flex text-center pt-2 w-100 justify-content-center" style="font-size: 0.75rem; color: #999;"> 705 © 2026 American Marketing & Publishing, L.L.C. All rights reserved. Names, logos, and other marks displayed are the property of their respective owners. 706 </div> 707 </div> 708</footer> 709 710 <div class="accessibility-img py-2 px-2"> 711 <div class="d-flex flex-wrap justify-content-center align-items-center text-center"> 712 <a href="/Accessibility?domain=maryscleaningcare" target="_blank" class="dark"> 713 <img src="/Content/Templates/images/accessibility_icon.png" height="60" width="42" /> 714 </a> 715 </div> 716 </div> 717 718 719 720 721<div class="chatbot-toggle-container"> 722 <button id="chatbot-toggle"> 723 <img src="/Content/Templates/images/chat_icon.png" height="60" width="60" /> 724 </button> 725</div> 726 727<!-- Chat Window --> 728<div id="chat-window"> 729 <!-- Chat Header --> 730 <div class="chat-header"> 731 <div class="chat-header-content"> 732 <h3 class="chat-header-title">Maryâs Cleaning Care Inc Assistant</h3> 733 </div>
734 <button id="close-chat" class="chat-close-btn" aria-label="Close chat"> 735 <i class="fas fa-times"></i> 736 </button> 737 </div> 738 739 <!-- Chat Messages --> 740 <div id="chat-messages"> 741 <div class="chat-message-container"> 742 <div class="chat-message"> 743 Hi there! How can we help you today? 744 </div> 745 </div> 746 </div> 747 748 <div id="disclaimer-info"> 749 <div class="disclaimer-content"> 750 <div class="disclaimer-text"> 751 AI Responses not guaranteed; verify before reliance 752 </div> 753 </div> 754 </div> 755 756 <!-- Cost Display --> 757 <div id="cost-info"> 758 <div class="cost-info-row"> 759 <div class="cost-info-left"> 760 <i class="fas fa-dollar-sign fa-xs cost-icon"></i> 761 <span id="cost-display">Cost: $0.00000</span> 762 </div> 763 <span id="percentage-display">0.0% of $0.02</span> 764 </div> 765 <div class="cost-progress-bar"> 766 <div id="cost-progress"></div> 767 </div> 768 </div> 769 770 <!-- Chat Input --> 771 <div class="chat-input-container"> 772 <form id="chat-form"> 773 <input type="text" id="chat-input" placeholder="Type your message..." /> 774 <button type="submit" id="send-message"> 775 <i class="fas fa-paper-plane"></i> 776 </button> 777 </form> 778 <div id="character-count">0/250</div> 779 <div id="character-limit-message" style="display: none; color: #dc2626; font-size: 12px; margin-top: 4px;"> 780 Your message is too long. Please limit your question to 250 characters or less. 781 </div> 782 </div> 783</div> 784 785<!-- Contact Form Window --> 786<div id="contact-form-window"> 787 <div class="contact-form-header"> 788 <h3 class="contact-form-title">Contact Form</h3> 789 <button id="close-contact-form" class="contact-form-close-btn" aria-label="Close contact form"> 790 <i class="fas fa-times"></i> 791 </button> 792 </div> 793 794 <div class="contact-form-content"> 795 <form id="chatbot-contact-form"> 796 <input type="hidden" id="chat_captcha_alt" name="chat_captcha_alt" value="F+uBSrMkrMsywsG3uIUzMi3u3fzINcFLlQ3YsycLG9bLVe80ollQTXhDRFtbH0Ba" /> 797 <input type="hidden" id="chat_g-Recaptcha-Response-Contact" name="chat_g-Recaptcha-Response-Contact" value="" /> 798 <input type="hidden" name="chat_aid" value="9a2815c2-398a-428e-be85-33d4b7ef02da" /> 799 <input type="hidden" name="chat_contactSubject" value="FrontDesk™ Contact Form" /> 800 <input type="hidden" name="chat_contactAddress" value="" /> 801 <input type="hidden" name="chat_contactZip" value="" /> 802 <input type="hidden" name="chat_contactCity" value="" /> 803 <input type="hidden" name="chat_contactState" value="" /> 804 805 <div class="ai-liability mt-1 mb-1">(Messages sent through this site are subject to third party moderation and are not encrypted or secure. We always try to respond as soon as possible, but off-hours inquiries may take us a little longer to reply.)</div> 806 807 <div class="form-group"> 808 <label for="chat_contactName" class="form-label">Name *</label> 809 <input type="text" id="chat_contactName" name="chat_contactName" class="form-input" required /> 810 </div> 811 812 <div class="form-group"> 813 <label for="chat_contactEmail" class="form-label">Email *</label> 814 <input type="email" id="chat_contactEmail" name="chat_contactEmail" class="form-input" required /> 815 </div> 816 817 <div class="form-group"> 818 <label for="chat_contactPhone" class="form-label">Phone</label> 819 <input type="tel" id="chat_contactPhone" name="chat_contactPhone" class="form-input" /> 820 </div> 821 822 <div class="form-group"> 823 <label for="chat_contactMessage" class="form-label">Message *</label> 824 <textarea id="chat_contactMessage" name="chat_contactMessage" rows="4" class="form-textarea" required></textarea> 825 </div> 826 827 <div id="classicCaptcha" class="d-flex flex-column"> 828 <div class="g-recaptcha ml-auto mb-3 captcha-responsive" data-sitekey="6LeprWcUAAAAAF533Xd5p6kCrnRs0z4ceJieYgeO"></div> 829 </div> 830 831 <!-- Sticky footer section with reCAPTCHA disclaimer and submit button --> 832 <div class="form-sticky-footer"> 833 <div class="grecaptcha-info"> 834 This site is protected by reCAPTCHA and the Google 835 <a href="https://policies.google.com/privacy">Privacy Policy</a> and 836 <a href="https://policies.google.com/terms">Terms of Service</a> apply. 837 </div> 838 <button type="submit" id="contact-submit-btn" class="form-submit-btn">Submit</button> 839 </div> 840 </form> 841 </div> 842</div> 843
844<script> 845 // Wrap entire chatbot functionality in a namespace to prevent conflicts 846 window.ChatbotModule = (function() { 847 'use strict'; 848 849 // Private variables 850 let isOpen = false; 851 let contactFormOpen = false; 852 let messages = [ 853 { role: 'assistant', content: 'Hi there! How can we help you today?' } 854 ]; 855 let isLoading = false; 856 let showContactForm = false; 857 let limitReached = false; 858 let apiCost = 0; 859 const maxCost = 0.5; 860 const maxQuestionLength = 250; 861 862 // Private element references 863 let $chatbotToggle; 864 let $chatWindow; 865 let $contactFormWindow; 866 let $closeChat; 867 let $closeContactForm; 868 let $chatMessages; 869 let $chatForm; 870 let $chatInput; 871 let $sendMessage; 872 let $characterCount; 873 let $costDisplay; 874 let $percentageDisplay; 875 let $costProgress; 876 let $contactForm; 877 878 // Initialize element references 879 function initializeElements() { 880 $chatbotToggle = $('#chatbot-toggle'); 881 $chatWindow = $('#chat-window'); 882 $contactFormWindow = $('#contact-form-window'); 883 $closeChat = $('#close-chat'); 884 $closeContactForm = $('#close-contact-form'); 885 $chatMessages = $('#chat-messages'); 886 $chatForm = $('#chat-form'); 887 $chatInput = $('#chat-input'); 888 $sendMessage = $('#send-message'); 889 $characterCount = $('#character-count'); 890 $costDisplay = $('#cost-display'); 891 $percentageDisplay = $('#percentage-display'); 892 $costProgress = $('#cost-progress'); 893 $contactForm = $('#chatbot-contact-form'); 894 } 895 896 // Initialize 897 function init() { 898 // Initialize element references 899 initializeElements(); 900 901 // Load cost from localStorage 902 const savedCost = localStorage.getItem('api_cost'); 903 if (savedCost) { 904 apiCost = parseFloat(savedCost); 905 906 // Check if cost limit is reached 907 if (apiCost >= maxCost) { 908 limitReached = true; 909 updateChatInputState(); 910 } 911 } 912 913 // Initialize character count display 914 updateCharacterCount(); 915 916 document.documentElement.style.setProperty('--adaptive-text-color', '#374151'); 917 document.documentElement.style.setProperty('--image-invert', 'none'); 918 919 setTimeout(updateTextColors, 100); 920 921 // Ensure chatbot stays on top even when modals are opened 922 $(document).on('shown.bs.modal', function (e) { 923 // Temporarily boost chatbot z-index when modals are shown 924 $('.chatbot-toggle-container').css('z-index', '2147483647'); 925 $('#chat-window').css('z-index', '2147483646'); 926 $('#contact-form-window').css('z-index', '2147483645'); 927 }); 928 929 // Set up event listeners 930 setupEventListeners(); 931 } 932 933 // Event Listeners with namespacing 934 function setupEventListeners() { 935 // Remove any existing chatbot event listeners to prevent duplicates 936 $chatbotToggle.off('.chatbot'); 937 $closeChat.off('.chatbot'); 938 $closeContactForm.off('.chatbot'); 939 $chatForm.off('.chatbot'); 940 $chatInput.off('.chatbot'); 941 $contactForm.off('.chatbot'); 942 943 // Toggle chat window 944 $chatbotToggle.on('click.chatbot', function(e) { 945 e.preventDefault(); 946 e.stopPropagation(); 947 toggleChat(); 948 }); 949 950 $closeChat.on('click.chatbot', function(e) { 951 e.preventDefault(); 952 e.stopPropagation(); 953 closeChat(); 954 }); 955 956 $closeContactForm.on('click.chatbot', function(e) { 957 e.preventDefault(); 958 e.stopPropagation(); 959 hideContactForm(); 960 }); 961 962 // Send message 963 $chatForm.on('submit.chatbot', function (e) { 964 e.preventDefault(); 965 e.stopPropagation(); 966 967 // Check if submit button is disabled 968 if ($sendMessage.prop('disabled') || $sendMessage.hasClass('disabled')) { 969 return false; 970 } 971 972 sendMessage(); 973 }); 974 975 // Character count - update on every keystroke 976 $chatInput.on('input.chatbot', function(e) { 977 // Only handle if this is specifically the chatbot input 978 if ($(this).attr('id') === 'chat-input') { 979 updateCharacterCount(); 980 } 981 }); 982 983 // Also check on keyup to catch paste events 984 $chatInput.on('keyup.chatbot paste.chatbot', function(e) { 985 // Only handle if this is specifically the chatbot input 986 if ($(this).attr('id') === 'chat-input') { 987 setTimeout(updateCharacterCount, 10); 988 } 989 }); 990 991 // Handle "Enter" key for sending messages 992 $chatInput.on('keydown.chatbot', function (e) { 993 // Only handle if this is specifically the chatbot input 994 if ($(this).attr('id') !== 'chat-input') { 995 return; 996 } 997 998 if (e.key === 'Enter' && !e.shiftKey && !limitReached) { 999 e.preventDefault(); 1000 e.stopImmediatePropagation(); 1001 1002 // Check if submit button is disabled 1003 if ($sendMessage.prop('disabled') || $sendMessage.hasClass('disabled')) { 1004 return false; 1005 } 1006 1007 sendMessage(); 1008 return; 1009 } 1010 }); 1011 1012 // Contact form submission 1013 $contactForm.on('submit.chatbot', function (e) { 1014 e.preventDefault(); 1015 e.stopPropagation(); 1016 submitContactForm(); 1017 }); 1018 } 1019 1020 // Toggle chat window 1021 function toggleChat() { 1022 if (!isOpen) { 1023 // Opening the chat 1024 isOpen = true; 1025 1026 // Hide toggle button on both mobile and desktop 1027 $('.chatbot-toggle-container').addClass('hide-toggle'); 1028 1029 // First show the window (still off-screen) 1030 $chatWindow.show(); 1031 1032 // Force a reflow to ensure the transition will work 1033 $chatWindow[0].offsetHeight; 1034 1035 // Then add the class that triggers the slide-in animation 1036 setTimeout(function () { 1037 $chatWindow.addClass('show'); 1038 scrollToBottom(); 1039 }, 10); 1040 } else { 1041 // Closing the chat 1042 isOpen = false; 1043 1044 // Show toggle button again 1045 $('.chatbot-toggle-container').removeClass('hide-toggle'); 1046 1047 // Remove the show class to trigger slide-out animation 1048 $chatWindow.removeClass('show'); 1049 1050 // Hide the window after the animation completes 1051 setTimeout(function () { 1052 $chatWindow.hide(); 1053 }, 300); // Match the CSS transition duration 1054 1055 // Also hide contact form if open 1056 if (contactFormOpen) { 1057 hideContactForm(); 1058 } 1059 } 1060 } 1061 1062 // Close chat window 1063 function closeChat() { 1064 if (isOpen) { 1065 isOpen = false; 1066 1067 // Show toggle button again 1068 $('.chatbot-toggle-container').removeClass('hide-toggle'); 1069 1070 // Trigger slide-out animation 1071 $chatWindow.removeClass('show'); 1072 1073 // Hide after animation 1074 setTimeout(function () { 1075 $chatWindow.hide(); 1076 }, 300); 1077 1078 if (contactFormOpen) { 1079 hideContactForm(); 1080 } 1081 } 1082 } 1083 1084 // Update character count and submit button state 1085 function updateCharacterCount() { 1086 const count = $chatInput.val().length; 1087 const remaining = maxQuestionLength - count; 1088 const $limitMessage = $('#character-limit-message'); 1089 1090 if (count > maxQuestionLength) { 1091 // Over limit - disable submit button and show message 1092 $characterCount.text(`${count}/${maxQuestionLength} (${Math.abs(remaining)} over limit)`).css('color', '#dc2626'); 1093 $limitMessage.text(`Your message is too long. Please limit your question to ${maxQuestionLength} characters or less. Your message is currently ${count} characters.`).show(); 1094 $chatInput.addClass('over-limit'); 1095 1096 // Disable submit button with proper styling 1097 $sendMessage.prop('disabled', true).addClass('disabled'); 1098 $sendMessage.css({ 1099 'background-color': '#9ca3af', 1100 'color': '#6b7280', 1101 'cursor': 'not-allowed', 1102 'opacity': '0.5', 1103 'pointer-events': 'none' 1104 }); 1105 1106 } else if (remaining <= 20) { 1107 // Close to limit - show warning but allow submit 1108 $characterCount.text(`${count}/${maxQuestionLength}`).css('color', '#f59e0b'); 1109 $limitMessage.hide(); 1110 $chatInput.removeClass('over-limit'); 1111 1112 // Enable submit button and reset styling 1113 $sendMessage.prop('disabled', false).removeClass('disabled'); 1114 $sendMessage.css({ 1115 'background-color': '', 1116 'color': '', 1117 'cursor': '', 1118 'opacity': '', 1119 'pointer-events': '' 1120 }); 1121 1122 } else {
1123 // Within limit - normal state 1124 $characterCount.text(`${count}/${maxQuestionLength}`).css('color', '#6b7280'); 1125 $limitMessage.hide(); 1126 $chatInput.removeClass('over-limit'); 1127 1128 // Enable submit button and reset styling 1129 $sendMessage.prop('disabled', false).removeClass('disabled'); 1130 $sendMessage.css({ 1131 'background-color': '', 1132 'color': '', 1133 'cursor': '', 1134 'opacity': '', 1135 'pointer-events': '' 1136 }); 1137 } 1138 } 1139 1140 // Send message 1141 function sendMessage() { 1142 const message = $chatInput.val().trim(); 1143 if (!message || isLoading || limitReached) return; 1144 1145 // Double check character limit including whitespace 1146 if (message.length > maxQuestionLength) { 1147 appendMessage('assistant', `Your message is too long. Please limit your question to ${maxQuestionLength} characters or less. Your message is currently ${message.length} characters.`); 1148 return; 1149 } 1150 1151 // Add user message to chat 1152 messages.push({ role: 'user', content: message }); 1153 appendMessage('user', message); 1154 1155 // Clear input 1156 $chatInput.val(''); 1157 updateCharacterCount(); 1158 1159 // Send to API 1160 fetchChatResponse(messages); 1161 } 1162 1163 // Fetch chat response from API 1164 function fetchChatResponse(messages) { 1165 isLoading = true; 1166 showLoadingIndicator(); 1167 1168 $.ajax({
1169 url: '/Home/Chat', 1170 type: 'POST', 1171 contentType: 'application/json', 1172 data: JSON.stringify({ 1173 Messages: messages, 1174 ProfileId: '40420' 1175 }), 1176 headers: { 1177 'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val() 1178 }, 1179 success: function (response) { 1180 hideLoadingIndicator(); 1181 1182 if (response.error) { 1183 appendMessage('assistant', `Error: ${response.error}`); 1184 console.error('API Error:', response.error); 1185 isLoading = false; 1186 return; 1187 } 1188 1189 // Update cost 1190 apiCost = response.Cost; 1191 localStorage.setItem('api_cost', apiCost.toString()); 1192 1193 // Check if limit reached 1194 if (response.LimitReached) { 1195 limitReached = true; 1196 showContactFormWindow(); 1197 appendMessage('assistant', 'Our AI chat has reached its capacity. Please use the contact form to get in touch directly with us.'); 1198 } else { 1199 // Check if response contains contact form trigger 1200 if (response.Message.includes('<start_contact_form>')) { 1201 const cleanResponse = response.Message.replace('<start_contact_form>', ''); 1202 appendMessage('assistant', cleanResponse); 1203 showContactFormWindow(); 1204 } else { 1205 appendMessage('assistant', response.Message); 1206 } 1207 1208 // Add response to messages array 1209 messages.push({ role: 'assistant', content: response.Message }); 1210 } 1211 1212 isLoading = false; 1213 updateChatInputState(); 1214 }, 1215 error: function (xhr, status, error) { 1216 hideLoadingIndicator(); 1217 appendMessage('assistant', 'Sorry, I encountered an error. Please try again later.'); 1218 console.error('AJAX Error:', error); 1219 isLoading = false; 1220 } 1221 }); 1222 } 1223 1224 // Append message to chat 1225 function appendMessage(role, content) { 1226 content = convertMarkdownLinksToHTML(content); 1227 const rootStyles = getComputedStyle(document.documentElement); 1228 const customerColor = rootStyles.getPropertyValue('--customer_color').trim(); 1229 const adaptivefontColor = rootStyles.getPropertyValue('--adaptive-text-color').trim(); 1230 1231 const messageClass = role === 'user' ? 'flex-end' : 'flex-start'; 1232 const bgColor = role === 'user' ? customerColor : 'white'; 1233 const textColor = role === 'user' ? adaptivefontColor : '#374151'; 1234 const borderRadius = role === 'user' ? 'border-bottom-right-radius: 0;' : 'border-bottom-left-radius: 0;'; 1235 const border = role === 'user' ? '' : 'border: 1px solid #e5e7eb;'; 1236 1237 const messageHtml = ` 1238 <div style="display: flex; justify-content: ${messageClass}; margin-bottom: 16px;"> 1239 <div style="max-width: 75%; padding: 12px; border-radius: 8px; background-color: ${bgColor}; ${border} ${borderRadius} color: ${textColor}; word-wrap: break-word; overflow-wrap: break-word;"> 1240 ${content} 1241 </div> 1242 </div> 1243 `; 1244 1245 $chatMessages.append(messageHtml); 1246 scrollToBottom(); 1247 } 1248 1249 // Show loading indicator 1250 function showLoadingIndicator() { 1251 const loadingHtml = ` 1252 <div id="loading-indicator" style="display: flex; justify-content: flex-start; margin-bottom: 16px;"> 1253 <div style="max-width: 75%; padding: 12px; border-radius: 8px; background-color: white; border: 1px solid #e5e7eb; color: #374151; display: flex; gap: 4px;"> 1254 <div class="loading-dot" style="width: 8px; height: 8px; background-color: #9ca3af; border-radius: 50%;"></div> 1255 <div class="loading-dot" style="width: 8px; height: 8px; background-color: #9ca3af; border-radius: 50%;"></div> 1256 <div class="loading-dot" style="width: 8px; height: 8px; background-color: #9ca3af; border-radius: 50%;"></div> 1257 </div> 1258 </div> 1259 `; 1260 1261 $chatMessages.append(loadingHtml); 1262 animateLoadingDots(); 1263 scrollToBottom(); 1264 1265 // Disable input while loading 1266 $chatInput.prop('disabled', true); 1267 $sendMessage.prop('disabled', true); 1268 } 1269 1270 // Animate loading dots 1271 function animateLoadingDots() { 1272 if (!$("#loading-indicator").length) return; 1273 1274 $(".loading-dot").each(function (index) { 1275 var $dot = $(this); 1276 setTimeout(function () { 1277 $dot.animate({ opacity: 0.3 }, 300) 1278 .animate({ opacity: 1 }, 300); 1279 }, index * 200); 1280 }); 1281 1282 setTimeout(function () { 1283 animateLoadingDots(); 1284 }, 900); 1285 } 1286 1287 // Hide loading indicator 1288 function hideLoadingIndicator() { 1289 $('#loading-indicator').remove(); 1290 1291 // Enable input after loading (unless limit reached) 1292 if (!limitReached) { 1293 $chatInput.prop('disabled', false); 1294 updateCharacterCount(); // This will set the correct submit button state 1295 } 1296 } 1297 1298 // Scroll to bottom of chat 1299 function scrollToBottom() { 1300 $chatMessages.scrollTop($chatMessages[0].scrollHeight); 1301 } 1302 1303 // Update cost display 1304 function updateCostDisplay() { 1305 $("#cost-info").show(); 1306 $costDisplay.text(`Cost: $${apiCost.toFixed(5)}`); 1307 1308 const percentage = (apiCost / maxCost) * 100; 1309 $percentageDisplay.text(`${percentage.toFixed(1)}% of $${maxCost.toFixed(2)}`); 1310 1311 $costProgress.css('width', `${percentage}%`); 1312
1313 // Change progress bar color if near limit 1314 if (percentage > 80) { 1315 $costProgress.css("background-color", "#dc2626"); 1316 } else { 1317 $costProgress.css("background-color", "#4f46e5"); 1318 } 1319 1320 // Update chat input state 1321 updateChatInputState(); 1322 } 1323 1324 // Update chat input state based on limit 1325 function updateChatInputState() { 1326 if (limitReached) { 1327 $chatInput.prop('disabled', true); 1328 $sendMessage.prop('disabled', true); 1329 $chatInput.attr('placeholder', 'Chat Unavailable.'); 1330 } else { 1331 $chatInput.prop('disabled', false); 1332 updateCharacterCount(); // This will set the correct submit button state 1333 $chatInput.attr('placeholder', 'Type your message...'); 1334 } 1335 } 1336 1337 // Show contact form 1338 function showContactFormWindow() { 1339 if (!contactFormOpen) { 1340 contactFormOpen = true; 1341 1342 // Hide toggle button when contact form opens 1343 $('.chatbot-toggle-container').addClass('hide-toggle'); 1344 1345 if (isMobile()) { 1346 // On mobile, slide out chat and slide in contact form 1347 $chatWindow.removeClass('show'); 1348 setTimeout(function () { 1349 $chatWindow.hide(); 1350 $contactFormWindow.show(); 1351 // Force reflow 1352 $contactFormWindow[0].offsetHeight; 1353 // Add show class for slide animation 1354 setTimeout(function () { 1355 $contactFormWindow.addClass('show'); 1356 $('.contact-form-content').scrollTop(0); 1357 }, 10); 1358 }, 300); 1359 } else { 1360 // Desktop - slide chat to left and slide in contact form 1361 $chatWindow.animate({ right: '390px' }, 300); 1362 $contactFormWindow.show(); 1363 setTimeout(function () { 1364 $contactFormWindow.animate({ right: '20px' }, 300); 1365 }, 50); 1366 } 1367 } 1368 } 1369 1370 // Hide contact form - slides out and chat returns to original position 1371 function hideContactForm() { 1372 if (contactFormOpen) { 1373 contactFormOpen = false; 1374 1375 if (isMobile()) { 1376 // Slide out contact form 1377 $contactFormWindow.removeClass('show'); 1378 1379 setTimeout(function () { 1380 $contactFormWindow.hide(); 1381 // Show chat window again if it was open 1382 if (isOpen) { 1383 $chatWindow.show(); 1384 // Force reflow 1385 $chatWindow[0].offsetHeight; 1386 setTimeout(function () { 1387 $chatWindow.addClass('show'); 1388 }, 10); 1389 } else { 1390 // If chat is not open, show toggle button 1391 $('.chatbot-toggle-container').removeClass('hide-toggle'); 1392 } 1393 }, 300); 1394 } else { 1395 // Desktop - animate back 1396 $contactFormWindow.animate({ right: '-370px' }, 300, function () { 1397 $contactFormWindow.hide(); 1398 }); 1399 $chatWindow.animate({ right: '20px' }, 300); 1400 1401 // Only show toggle if both windows are closed 1402 if (!isOpen) { 1403 $('.chatbot-toggle-container').removeClass('hide-toggle'); 1404 } 1405 } 1406 } 1407 } 1408
1409 // Submit contact form 1410 function submitContactForm() { 1411 const name = $('#chat_contactName').val(); 1412 const email = $('#chat_contactEmail').val(); 1413 const phone = $('#chat_contactPhone').val(); 1414 const message = $('#chat_contactMessage').val(); 1415 1416 // Basic validation 1417 if (!name || !email || !message) { 1418 alert('Please fill out all required fields.'); 1419 return; 1420 } 1421 1422 // Email validation 1423 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 1424 if (!emailRegex.test(email)) { 1425 alert('Please enter a valid email address.'); 1426 return; 1427 } 1428 1429 // Message length validation 1430 if (message.length < 10) { 1431 alert('Your message must be at least 10 characters long.'); 1432 return; 1433 } 1434 1435 // Disable form during submission 1436 $('#contact-submit-btn').prop('disabled', true).text('Sending...'); 1437 1438 var form = $("#chatbot-contact-form"); 1439 1440 // Execute reCAPTCHA before submitting 1441 grecaptcha.ready(function() { 1442 grecaptcha.execute('6LcokacUAAAAAH_snk3O5bpdYwjpC5XAuXNLrJLH', {action: 'contact_us'}).then(function(token) { 1443 // Set the reCAPTCHA token 1444 $("#chat_g-Recaptcha-Response-Contact").val(token); 1445 1446 // Submit the form with reCAPTCHA token 1447 $.ajax({
1448 url: "/Home/ChatContactPost", 1449 type: 'POST', 1450 data: form.serialize(), 1451 success: function (response) { 1452 if (typeof response === 'string' && response.indexOf('{"message":') != -1) { 1453 var jsonData = JSON.parse(response); 1454 if (jsonData.success) { 1455 appendMessage('assistant', 'Thanks for providing your information! We will get back to you soon.'); 1456 hideContactForm(); 1457 1458 // Reset the form 1459 $('#chat_contactName').val(''); 1460 $('#chat_contactEmail').val(''); 1461 $('#chat_contactPhone').val(''); 1462 $('#chat_contactMessage').val(''); 1463 } else { 1464 alert(jsonData.message || 'There was an error processing your request.'); 1465 } 1466 } else if (response.success) { 1467 appendMessage('assistant', 'Thanks for providing your information! We will get back to you soon.'); 1468 hideContactForm(); 1469 1470 // Reset the form 1471 $('#chat_contactName').val(''); 1472 $('#chat_contactEmail').val(''); 1473 $('#chat_contactPhone').val(''); 1474 $('#chat_contactMessage').val(''); 1475 1476 } else { 1477 alert(response.message || 'There was an error processing your request.'); 1478 } 1479 1480 $('#contact-submit-btn').prop('disabled', false).text('Submit'); 1481 }, 1482 error: function (xhr, status, error) { 1483 alert('Failed to submit your information. Please try again.'); 1484 console.error('AJAX Error:', error); 1485 $('#contact-submit-btn').prop('disabled', false).text('Submit'); 1486 } 1487 }); 1488 }); 1489 }); 1490 } 1491 1492 function convertMarkdownLinksToHTML(text) { 1493 return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, linkText, url) => { 1494 // Add target="_blank" for http/https links 1495 if (url.startsWith('http://') || url.startsWith('https://')) { 1496 return `<a href="${url}" target="_blank" rel="noopener noreferrer">${linkText}</a>`; 1497 } 1498 // Keep tel: and mailto: links as-is 1499 return `<a href="${url}">${linkText}</a>`; 1500 }); 1501 } 1502 1503 function getLuminance(hex) { 1504 // Remove # if present 1505 hex = hex.replace('#', ''); 1506 1507 // Convert hex to RGB 1508 const r = parseInt(hex.substr(0, 2), 16) / 255; 1509 const g = parseInt(hex.substr(2, 2), 16) / 255; 1510 const b = parseInt(hex.substr(4, 2), 16) / 255; 1511 1512 // Calculate relative luminance 1513 const rLum = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); 1514 const gLum = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4); 1515 const bLum = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4); 1516 1517 return 0.2126 * rLum + 0.7152 * gLum + 0.0722 * bLum; 1518 } 1519 1520 function isLightColor(hex) { 1521 const luminance = getLuminance(hex); 1522 return luminance > 0.5; // Threshold for light vs dark 1523 } 1524 1525 function updateTextColors() { 1526 const rootStyles = getComputedStyle(document.documentElement); 1527 let customerColor = rootStyles.getPropertyValue('--customer_color').trim(); 1528 1529 // Check if customer color is not set or is empty 1530 if (!customerColor || customerColor === '') { 1531 const defaultColor = '#f3f3f3'; 1532 1533 // Set the default customer color 1534 document.documentElement.style.setProperty('--customer_color', defaultColor); 1535 customerColor = defaultColor; 1536 } 1537 1538 const isLight = isLightColor(customerColor); 1539 const textColor = isLight ? '#000000b3' : '#ffffff'; 1540 1541 const $chatbotToggle = $('#chatbot-toggle'); 1542 if (isLight) { 1543 $chatbotToggle.removeClass('dark-background').addClass('light-background'); 1544 } else { 1545 $chatbotToggle.removeClass('light-background').addClass('dark-background'); 1546 } 1547 1548 // Update the CSS custom property 1549 document.documentElement.style.setProperty('--adaptive-text-color', textColor); 1550 } 1551 1552 function isMobile() { 1553 return window.innerWidth <= 768; 1554 } 1555 1556 // Public interface 1557 return { 1558 init: init 1559 }; 1560 })(); 1561 1562 // Initialize the chatbot module when document is ready 1563 $(document).ready(function () { 1564 if (window.ChatbotModule) {
1565 window.ChatbotModule.init(); 1566 } 1567 }); 1568</script>
1568 1569<div id="loadingImg" class="loader" style="display:none !important"> 1570 <i class="fas fa-circle"></i> 1571 <i class="fas fa-circle"></i> 1572 <i class="fas fa-circle"></i> 1573</div> 1574
1575<script type="text/javascript"> 1576 var isMobile = false; 1577 var isTablet = false; 1578 var itemsVisible = false; // false indicates that items are initially hidden 1579 var pageSize = 5; 1580 var pageIndex = 0; 1581 var hasbanner = false; 1582 var keepPostUp = false; 1583 var listOfSites = ""; 1584 var testimonialPageBackgroundColor = '#4DCCAA'; 1585 var numOfPosts = 25; 1586 var postsPageIndex = 0; 1587 var geocode = null; 1588 var selectedServiceId = null; 1589 var servicesRendered = false; 1590 var aboutRendered = false; 1591 var testimonialsRendered = false; 1592 var contactRendered = false; 1593 var photosRendered = false; 1594 var faqRendered = false; 1595 var locationsRendered = false; 1596 var additionalContentRendered = false; 1597 var isSubmittingReview = false; //global flag to track submission 1598 1599 $(document).ready(function () { 1600 if ($("#contact_us_phone").length > 0) { 1601 var cleave = new Cleave('#contact_us_phone', { 1602 phone: true, 1603 phoneRegionCode: 'US' 1604 }); 1605 } 1606 1607 checkMobile(); 1608 checkTablet(); 1609 checkHashRoute(); 1610 PauseVideoPlayer(); 1611 1612 if(hasbanner){ 1613 GetPostsBanner(); 1614 } 1615 1616 $(window).on('hashchange', function(){ 1617 checkHashRoute(); 1618 }); 1619 1620 var navMain = $("#navbarToggler"); 1621 navMain.on("click", "a", null, function () { 1622 navMain.collapse('hide'); 1623 }); 1624 1625 document.addEventListener("visibilitychange", function () { 1626 if (document["hidden"]) { 1627 PauseVideoPlayer(); 1628 } 1629 }); 1630 1631 $(window).scroll(function () { 1632 var scroll = $(window).scrollTop(); 1633 if (('False' == 'False') || ('False' == 'True' && window.outerWidth > 767.98)) { 1634 if (scroll > 150){ 1635 $(".content").addClass("page-section-adjust"); 1636 $(".nav-logo").addClass("nav-logo-adjust"); 1637 $(".nav-location").addClass("nav-item-adjust"); 1638 } 1639 else if(scroll <= 150){ 1640 $(".content").removeClass("page-section-adjust"); 1641 $(".nav-logo").removeClass("nav-logo-adjust"); 1642 $(".nav-location").removeClass("nav-item-adjust"); 1643 } 1644 } 1645 }); 1646 1647 $(document).on("click", "#btnContactPost", function (e) { 1648 $("#btnContactPost").prop('disabled', true); 1649 $("#btnContactPost").css('font-weight', 'normal'); 1650 contactUsPost(e); 1651 }); 1652 1653 var analyticsCategory = "Desktop"; 1654 if (isMobile == true) { 1655 analyticsCategory = "Mobile"; 1656 if ($(window).width() < 576) { 1657 if ($("#headernav").hasClass('justify-content-between')) { 1658 $("#headernav").removeClass('justify-content-between'); 1659 } 1660 if (!$("#headernav").hasClass('justify-content-center')) { 1661 $("#headernav").addClass('justify-content-center'); 1662 } 1663 if ($("#footer-address").hasClass('justify-content-center')) { 1664 $("#footer-address").removeClass('justify-content-center'); 1665 } 1666 if ($("#footer-hours").hasClass('justify-content-center')) { 1667 $("#footer-hours").removeClass('justify-content-center'); 1668 } 1669 if ($("#footer-sitenav").hasClass('justify-content-center')) { 1670 $("#footer-sitenav").removeClass('justify-content-center'); 1671 } 1672 } 1673 } 1674 1675 if (testimonialPageBackgroundColor != null && testimonialPageBackgroundColor != '' && testimonialPageBackgroundColor != "#F5F5F5") { 1676 //set footer background color 1677 $('.custom-background-color').css('background-color', testimonialPageBackgroundColor); 1678 } 1679 else { 1680 //set footer background color to white 1681 var white = "#FFFFFF"; 1682 $('.custom-background-color').css('background-color', white); 1683 } 1684 1685 $("#HomePageNavigationLink").click(function(e){
1686 $('html, body').animate({ scrollTop: 0 }, 'fast'); 1687 }); 1688 1689 $(".business-name").click(function(e){ 1690 $('html, body').animate({ scrollTop: 0 }, 'fast'); 1691 }); 1692 1693 if(numOfPosts > 0){ 1694 GetPostsData(0); 1695 } 1696 1697 $(document).on('click', "#nextPosts", function (e) { 1698 ScrollForPosts(e); 1699 GetPostsData(1); 1700 }); 1701 1702 $(document).on('click', "#prevPosts", function (e) { 1703 ScrollForPosts(e); 1704 GetPostsData(-1); 1705 }); 1706 1707 $(document).on('click', "#postReview", function (e) { 1708 e.preventDefault(); 1709 e.stopPropagation(); 1710 1711 if (isSubmittingReview) { 1712 console.log('Review submission already in progress - blocking duplicate submission'); 1713 return false; 1714 } 1715 1716 var $button = $(this); 1717 var $form = $("#postReviewForm"); 1718 var $modal = $("#reviewFormSection"); 1719 1720 isSubmittingReview = true; 1721 console.log('Starting review submission'); 1722 1723 setProcessingState($button, $form, true); 1724 1725 var alertType = "success"; 1726 1727 if(validateReview("#postReview")) { 1728 console.log('Review validation passed'); 1729 ShowLoading(); 1730 1731 grecaptcha.ready(function() { 1732 grecaptcha.execute('6LcokacUAAAAAH_snk3O5bpdYwjpC5XAuXNLrJLH', {action: 'testimonial_review'}).then(function(token) { 1733 $("#g-Recaptcha-Response-Review").attr("value", token); 1734 1735 var rating = null; 1736 var selectElement = document.getElementById('starRating'); 1737 var selectRating = selectElement ? selectElement.value : null; 1738 var selectedOption = $('#starRating option:selected').val(); 1739 var barRating = null; 1740 var reviewerAlias = $('#review_alias').val() || ''; 1741 var reviewHeading = $('#review_heading').val() || ''; 1742 var antiForgeryToken = $('input[name="__RequestVerificationToken"]').val(); 1743 var recaptchaToken = $('#g-Recaptcha-Response-Review').val(); 1744 var timeToPost = $('#phoneNumber_alternative').val(); 1745 1746 var reviewText = $('#review').val() || ''; 1747 var profileId = 40420; 1748 1749 console.log('Trying to get rating value...'); 1750 1751 var selectRating = $('#starRating')[0] ? $('#starRating')[0].value : null; 1752 console.log('Select element direct value:', selectRating); 1753 1754 var jqueryRating = $('#starRating').val(); 1755 console.log('jQuery val() result:', jqueryRating, typeof jqueryRating); 1756 1757 if (typeof $('#starRating').barrating === 'function') { 1758 try { 1759 var barResult = $('#starRating').barrating('show'); 1760 1761 if (barResult && typeof barResult === 'string') { 1762 barRating = barResult; 1763 } else if (barResult && typeof barResult === 'number') { 1764 barRating = barResult.toString(); 1765 } 1766 } catch (e) { 1767 console.log('Error getting barrating value:', e); 1768 } 1769 } 1770 1771 var selectedOption = $('#starRating option:selected').val(); 1772 console.log('Selected option value:', selectedOption); 1773 1774 var jqueryResult = $('#starRating').val(); 1775 var jqueryRating = null; 1776 1777 if (jqueryResult && typeof jqueryResult === 'string') { 1778 jqueryRating = jqueryResult; 1779 } else if (jqueryResult && typeof jqueryResult === 'number') { 1780 jqueryRating = jqueryResult.toString(); 1781 } 1782 1783 if (barRating && barRating !== "" && barRating !== "0") { 1784 rating = barRating; 1785 } else if (selectRating && selectRating !== "" && selectRating !== "0") { 1786 rating = selectRating; 1787 } else if (selectedOption && selectedOption !== "" && selectedOption !== "0") { 1788 rating = selectedOption; 1789 } else if (jqueryRating && jqueryRating !== "" && jqueryRating !== "0") { 1790 rating = jqueryRating; 1791 } 1792 1793 if (rating && typeof rating !== 'string') { 1794 console.log('WARNING: Rating is not a string, converting:', rating, typeof rating);
1795 rating = String(rating); 1796 } 1797 1798 if (rating && typeof rating === 'object') { 1799 console.error('CRITICAL ERROR: Rating is still an object!', rating); 1800 rating = null; 1801 } 1802 1803 console.log('Validation check - rating found:', rating, typeof rating); 1804 console.log('Final rating value:', rating, typeof rating); 1805 1806 console.log('Form values before sending:'); 1807 console.log('- Review text:', reviewText); 1808 console.log('- Rating:', rating); 1809 console.log('- Profile ID:', profileId); 1810 console.log('- Reviewer alias:', reviewerAlias); 1811 console.log('- Review heading:', reviewHeading); 1812 console.log('- Anti-forgery token:', antiForgeryToken ? 'Present' : 'Missing'); 1813 1814 // Build form data 1815 var formDataArray = [ 1816 { name: 'profile_id', value: profileId }, 1817 { name: 'review', value: reviewText }, 1818 { name: 'review_rating', value: rating }, 1819 { name: 'reviewer_alias', value: reviewerAlias }, 1820 { name: 'review_heading', value: reviewHeading }, 1821 { name: '__RequestVerificationToken', value: antiForgeryToken }, 1822 { name: 'g-Recaptcha-Response-Review', value: recaptchaToken }, 1823 { name: 'phoneNumber_alternative', value: timeToPost }, 1824 { name: '_t', value: new Date().getTime() } 1825 ]; 1826 1827 var formData = formDataArray 1828 .filter(item => item.value !== undefined && item.value !== null && item.value !== '') 1829 .map(item => encodeURIComponent(item.name) + '=' + encodeURIComponent(item.value)) 1830 .join('&'); 1831 1832 console.log('Manual form data built:', formData); 1833 1834 $.ajax({
1835 url: "/Home/ReviewPost", 1836 type: "POST", 1837 data: formData, 1838 contentType: "application/x-www-form-urlencoded; charset=UTF-8", 1839 dataType: "json", 1840 success: function(data) { 1841 console.log('Review submission response:', data); 1842 1843 if (!data.message || data.message.trim() === '') { 1844 data.message = data.success ? 1845 'Thank you for your review! It has been submitted successfully.' : 1846 'There was an issue submitting your review. Please check all fields and try again.'; 1847 } 1848 1849 alertType = data.success ? "success" : "warning"; 1850 1851 if (!data.success) { 1852 recaptchaReviewIncomplete(); 1853 showClassicCaptcha("review"); 1854 } 1855 1856 $form.find('.alert').remove(); 1857 1858 var alertHtml = '<div class="alert alert-' + alertType + ' alert-dismissible" role="alert">' + 1859 '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + 1860 '<span aria-hidden="true">×</span></button>' + 1861 data.message + '</div>'; 1862 1863 $form.prepend(alertHtml); 1864 1865 $modal.find('.modal-body').scrollTop(0); 1866 1867 if(alertType === "success") { 1868 // Auto-hide modal after showing success message 1869 setTimeout(function () { 1870 $modal.modal('hide'); 1871 ClearReviewFields(); 1872 }, 3000); 1873 } 1874 1875 // Fade out alert after delay 1876 setTimeout(function() { 1877 $form.find('.alert').fadeOut(500, function() { 1878 $(this).remove(); 1879 }); 1880 }, 6000); 1881 }, 1882 error: function(xhr, status, error) { 1883 console.error('AJAX Error:', status, error); 1884 console.error('Response Text:', xhr.responseText); 1885 1886 $form.find('.alert').remove(); 1887 var errorHtml = '<div class="alert alert-danger alert-dismissible" role="alert">' + 1888 '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + 1889 '<span aria-hidden="true">×</span></button>' + 1890 'An error occurred while submitting your review. Please try again.</div>'; 1891 $form.prepend(errorHtml); 1892 1893 $modal.find('.modal-body').scrollTop(0); 1894 }, 1895 complete: function() { 1896 // Reset states 1897 console.log('Resetting processing state'); 1898 setProcessingState($button, $form, false); 1899 HideLoading(); 1900 isSubmittingReview = false; 1901 console.log('Review submission completed'); 1902 } 1903 }); 1904 1905 }).catch(function(error) { 1906 console.error('reCAPTCHA Error:', error); 1907 1908 $form.find('.alert').remove(); 1909 var captchaErrorHtml = '<div class="alert alert-danger alert-dismissible" role="alert">
1909' + 1910 '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + 1911 '<span aria-hidden="true">×</span></button>' + 1912 'Security verification failed. Please refresh the page and try again.</div>'; 1913 $form.prepend(captchaErrorHtml); 1914 1915 $modal.find('.modal-body').scrollTop(0); 1916 setProcessingState($button, $form, false); 1917 HideLoading(); 1918 isSubmittingReview = false; 1919 }); 1920 }); 1921 } else { 1922 console.log('Review validation failed'); 1923 1924 var missingFields = []; 1925 if (!$('#review').val() || $('#review').val().trim() === '') { 1926 missingFields.push('review text'); 1927 } 1928 1929 var rating = null; 1930 var selectRating = $('#starRating')[0] ? $('#starRating')[0].value : null; 1931 var jqueryRating = $('#starRating').val(); 1932 var selectedOption = $('#starRating option:selected').val(); 1933 var barRating = null; 1934 1935 if (typeof $('#starRating').barrating === 'function') { 1936 try { 1937 barRating = $('#starRating').barrating('show'); 1938 } catch (e) { 1939 // Ignore error 1940 } 1941 } 1942 1943 if (barRating && barRating !== "" && barRating !== "0") { 1944 rating = barRating; 1945 } else if (selectRating && selectRating !== "" && selectRating !== "0") { 1946 rating = selectRating; 1947 } else if (selectedOption && selectedOption !== "" && selectedOption !== "0") { 1948 rating = selectedOption; 1949 } else if (typeof jqueryRating === 'string' && jqueryRating !== "" && jqueryRating !== "0") { 1950 rating = jqueryRating; 1951 } 1952 1953 console.log('Validation check - rating found:', rating); 1954 1955 if (!rating || rating === "" || rating === "0") { 1956 missingFields.push('rating'); 1957 } 1958 1959 var validationMessage = 'Please fill out the following required fields: ' + missingFields.join(' and ') + '.'; 1960 console.log('Validation message:', validationMessage); 1961 1962 $form.find('.alert').remove(); 1963 var validationHtml = '<div class="alert alert-warning alert-dismissible" role="alert">' + 1964 '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + 1965 '<span aria-hidden="true">×</span></button>' + 1966 validationMessage + '</div>'; 1967 $form.prepend(validationHtml); 1968 1969 $modal.find('.modal-body').scrollTop(0); 1970 setProcessingState($button, $form, false); 1971 isSubmittingReview = false; 1972 1973 return false; 1974 } 1975 }); 1976 1977 $('#reviewFormSection').on('hidden.bs.modal', function () { 1978 console.log('Modal hidden - resetting submission state'); 1979 isSubmittingReview = false; 1980 setProcessingState($("#postReview"), $("#postReviewForm"), false); 1981 $(this).find('.alert').remove(); 1982 }); 1983 1984 $('#reviewFormSection').on('show.bs.modal', function () { 1985 console.log('Modal shown - ensuring clean state'); 1986 isSubmittingReview = false; 1987 setProcessingState($("#postReview"), $("#postReviewForm"), false); 1988 1989 $("#postReview").prop('disabled', false); 1990 $("#postReview").css('font-weight', 'bold'); 1991 1992 setTimeout(function () { 1993 console.log('=== STAR RATING DEBUG ==='); 1994 console.log('Star rating element:', $('#starRating')[0]); 1995 console.log('Star rating jQuery object:', $('#starRating')); 1996 console.log('Star rating direct value:', $('#starRating')[0] ? $('#starRating')[0].value : 'N/A'); 1997 console.log('Star rating jQuery val():', $('#starRating').val()); 1998 console.log('Star rating selected option:', $('#starRating option:selected').val()); 1999 console.log('Barrating plugin available:', typeof $('#starRating').barrating === 'function'); 2000 if (typeof $('#starRating').barrating === 'function') { 2001 try { 2002 console.log('Barrating current value:', $('#starRating').barrating('show')); 2003 } catch (e) { 2004 console.log('Error getting barrating value:', e); 2005 } 2006 } 2007 console.log('=== END STAR RATING DEBUG ==='); 2008 }, 100); 2009 }); 2010 2011 // Prevent modal from closing during submission 2012 $('#reviewFormSection').on('hide.bs.modal', function (e) { 2013 if (isSubmittingReview) { 2014 console.log('Preventing modal close during submission'); 2015 e.preventDefault(); 2016 return false; 2017 } 2018 }); 2019 2020 $('a').on('click', function () { 2021 if (this.id !== "undefined" && this.id !== "") { 2022 gtag('event', 'Link Click', { 2023 'event_category': analyticsCategory, 2024 'event_label': this.id 2025 }); 2026 } 2027 }); 2028 2029 $('#PhoneNav').on('click', function () { 2030 gtag('event', 'Link Click', { 2031 'event_category': analyticsCategory, 2032 'event_label': "CustomerBusinessPhoneLinkPlatinum1" 2033 }); 2034 }); 2035 $('.phoneLinkInput').on('click', function () { 2036 gtag('event', 'Link Click', { 2037 'event_category': analyticsCategory, 2038 'event_label': "CustomerBusinessPhoneLinkPlatinum1-1" 2039 }); 2040 }); 2041 $('#contactPhoneLink').on('click', function () { 2042 gtag('event', 'Link Click', { 2043 'event_category': analyticsCategory, 2044 'event_label': "CustomerBusinessPhoneLinkPlatinum1-2" 2045 }); 2046 }); 2047 $('#contactPrimaryPhoneLink').on('click', function () { 2048 gtag('event', 'Link Click', { 2049 'event_category': analyticsCategory, 2050 'event_label': "CustomerBusinessPhoneLinkPlatinum1-3" 2051 }); 2052 }); 2053 $('#contactSecondaryPhoneLink').on('click', function () { 2054 gtag('event', 'Link Click', { 2055 'event_category': analyticsCategory, 2056 'event_label': "CustomerBusinessPhoneLinkPlatinum1-4" 2057 }); 2058 }); 2059 $('#contactMobilePhoneLink').on('click', function () { 2060 gtag('event', 'Link Click', { 2061 'event_category': analyticsCategory, 2062 'event_label': "CustomerBusinessPhoneLinkPlatinum1-5" 2063 }); 2064 }); 2065 $('#contactFaxPhoneLink').on('click', function () { 2066 gtag('event', 'Link Click', { 2067 'event_category': analyticsCategory, 2068 'event_label': "CustomerBusinessPhoneLinkPlatinum1-6" 2069 }); 2070 }); 2071 $('#contactTollfreePhoneLink').on('click', function () { 2072 gtag('event', 'Link Click', { 2073 'event_category': analyticsCategory, 2074 'event_label': "CustomerBusinessPhoneLinkPlatinum1-7" 2075 }); 2076 }); 2077 $('#contactPhoneC1').on('click', function () { 2078 gtag('event', 'Link Click', { 2079 'event_category': analyticsCategory, 2080 'event_label': "CustomerContactPhoneLinkPlatinum1-8" 2081 }); 2082 }); 2083 $('#contactPhoneC2').on('click', function () { 2084 gtag('event', 'Link Click', { 2085 'event_category': analyticsCategory, 2086 'event_label': "CustomerContactPhoneLinkPlatinum1-9" 2087 }); 2088 }); 2089 $('#contactPhoneC3').on('click', function () { 2090 gtag('event', 'Link Click', { 2091 'event_category': analyticsCategory, 2092 'event_label': "CustomerContactPhoneLinkPlatinum1-10" 2093 }); 2094 }); 2095 $('#contactPhoneC4').on('click', function () { 2096 gtag('event', 'Link Click', { 2097 'event_category': analyticsCategory, 2098 'event_label': "CustomerContactPhoneLinkPlatinum1-11" 2099 }); 2100 }); 2101 $('#contactPhoneC5').on('click', function () { 2102 gtag('event', 'Link Click', { 2103 'event_category': analyticsCategory, 2104 'event_label': "CustomerContactPhoneLinkPlatinum1-12" 2105 }); 2106 }); 2107 $('#contactEmail').on('click', function () { 2108 gtag('event', 'Link Click', { 2109 'event_category': analyticsCategory, 2110 'event_label': "CustomerBusinessEmailLinkPlatinum1" 2111 }); 2112 }); 2113 $('#writeReviewBt').on('click', function () { 2114 gtag('event', 'Button Click', { 2115 'event_category': analyticsCategory, 2116 'event_label': "WriteReviewButtonPlatinum1" 2117 }); 2118 }); 2119 $(document).on('click', '#postReview', function () { 2120 gtag('event', 'Button Click', { 2121 'event_category': analyticsCategory, 2122 'event_label': "PostReviewButtonPlatinum1" 2123 }); 2124 }); 2125 $('#nextReview').on('click', function () { 2126 gtag('event', 'Button Click', { 2127 'event_category': analyticsCategory, 2128 'event_label': "ShowNextReviewsButtonPlatinum1" 2129 }); 2130 }); 2131 $('#prevReview').on('click', function () { 2132 gtag('event', 'Button Click', { 2133 'event_category': analyticsCategory, 2134 'event_label': "ShowPreviousReviewsButtonPlatinum1" 2135 }); 2136 }); 2137 $('#google-social').on('click', function () { 2138 gtag('event', 'Link Click', { 2139 'event_category': analyticsCategory, 2140 'event_label': "CustomerGoogleSocialLinkPlatinum1-1" 2141 }); 2142 }); 2143 $('#facebook-social').on('click', function () { 2144 gtag('event', 'Link Click', { 2145 'event_category': analyticsCategory, 2146 'event_label': "CustomerFacebookSocialLinkPlatinum1-1" 2147 }); 2148 }); 2149 $('#yelp-social').on('click', function () { 2150 gtag('event', 'Link Click', { 2151 'event_category': analyticsCategory, 2152 'event_label': "CustomerYelpSocialLinkPlatinum1-1" 2153 }); 2154 }); 2155 $('#twitter-social').on('click', function () { 2156 gtag('event', 'Link Click', { 2157 'event_category': analyticsCategory, 2158 'event_label': "CustomerTwitterSocialLinkPlatinum1-1" 2159 }); 2160 }); 2161 $('#linkedin-social').on('click', function () { 2162 gtag('event', 'Link Click', { 2163 'event_category': analyticsCategory, 2164 'event_label': "CustomerLinkedinSocialLinkPlatinum1-1" 2165 }); 2166 }); 2167 $('#instagram-social').on('click', function () { 2168 gtag('event', 'Link Click', { 2169 'event_category': analyticsCategory,
2170 'event_label': "CustomerInstagramSocialLinkPlatinum1-1" 2171 }); 2172 }); 2173 $('#google-social-footer').on('click', function () { 2174 gtag('event', 'Link Click', { 2175 'event_category': analyticsCategory, 2176 'event_label': "CustomerGoogleSocialLinkPlatinum1-2" 2177 }); 2178 }); 2179 $('#facebook-social-footer').on('click', function () { 2180 gtag('event', 'Link Click', { 2181 'event_category': analyticsCategory, 2182 'event_label': "CustomerFacebookSocialLinkPlatinum1-2" 2183 }); 2184 }); 2185 $('#yelp-social-footer').on('click', function () { 2186 gtag('event', 'Link Click', { 2187 'event_category': analyticsCategory, 2188 'event_label': "CustomerYelpSocialLinkPlatinum1-2" 2189 }); 2190 }); 2191 $('#twitter-social-footer').on('click', function () { 2192 gtag('event', 'Link Click', { 2193 'event_category': analyticsCategory, 2194 'event_label': "CustomerTwitterSocialLinkPlatinum1-2" 2195 }); 2196 }); 2197 $('#linkedin-social-footer').on('click', function () { 2198 gtag('event', 'Link Click', { 2199 'event_category': analyticsCategory, 2200 'event_label': "CustomerLinkedinSocialLinkPlatinum1-2" 2201 }); 2202 }); 2203 $('#instagram-social-footer').on('click', function () { 2204 gtag('event', 'Link Click', { 2205 'event_category': analyticsCategory, 2206 'event_label': "CustomerInstagramSocialLinkPlatinum1-2" 2207 }); 2208 }); 2209 $('#contactEmailFooter').on('click', function () { 2210 gtag('event', 'Link Click', { 2211 'event_category': analyticsCategory, 2212 'event_label': "CustomerBusinessEmailLinkPlatinum1-2" 2213 }); 2214 }); 2215 $('#btnContactPost').on('click', function () { 2216 gtag('event', 'Link Click', { 2217 'event_category': analyticsCategory, 2218 'event_label': "CustomerContactLeadPostPlatinum1" 2219 }); 2220 }); 2221 $("#external-review-link").on('click', function () { 2222 gtag('event', 'Link Click', { 2223 'event_category': analyticsCategory, 2224 'event_label': "CustomerExternalReviewLinkPlatinum1" 2225 }); 2226 }); 2227 $('#video').on('click', function () { 2228 gtag('event', 'Video Click', { 2229 'event_category': analyticsCategory, 2230 'event_label': "CustomerVideoPlatinum1" 2231 }); 2232 }); 2233 $('#website-social').on('click', function () { 2234 gtag('event', 'Link Click', { 2235 'event_category': analyticsCategory, 2236 'event_label': "CustomerWebsiteSocialLinkPlatinum1-1" 2237 }); 2238 }); 2239 $('#website-social-footer').on('click', function () { 2240 gtag('event', 'Link Click', { 2241 'event_category': analyticsCategory, 2242 'event_label': "CustomerWebsiteSocialLinkPlatinum1-2" 2243 }); 2244 }); 2245 $('#accessibility-social-footer').on('click', function () { 2246 gtag('event', 'Link Click', { 2247 'event_category': analyticsCategory, 2248 'event_label': "CustomerWebsiteAccessibilityLinkPlatinum1" 2249 }); 2250 }); 2251 $('#photos-image-element').on('click', function () { 2252 gtag('event', 'Link Click', { 2253 'event_category': analyticsCategory, 2254 'event_label': "CustomerWebsiteImageryPhotoPlatinum1" 2255 }); 2256 }); 2257 $('#HomePageNavigationLink').on('click', function () { 2258 gtag('event', 'Link Click', { 2259 'event_category': analyticsCategory, 2260 'event_label': "CustomerHomePageNavLinkPlatinum1" 2261 }); 2262 }); 2263 $('#AccessibilityPageNavigationLink').on('click', function () { 2264 gtag('event', 'Link Click', { 2265 'event_category': analyticsCategory, 2266 'event_label': "CustomerAccessibilityPageNavLinkPlatinum1" 2267 }); 2268 }); 2269 $('#ContactPageNavigationLink').on('click', function () { 2270 gtag('event', 'Link Click', { 2271 'event_category': analyticsCategory, 2272 'event_label': "CustomerContactPageNavLinkPlatinum1" 2273 }); 2274 }); 2275 $('#PhotosNavigationLink').on('click', function () { 2276 gtag('event', 'Link Click', { 2277 'event_category': analyticsCategory, 2278 'event_label': "CustomerPhotosPageNavLinkPlatinum1" 2279 }); 2280 }); 2281 $('#testimoinalsPageNavigationLink').on('click', function () { 2282 gtag('event', 'Link Click', { 2283 'event_category': analyticsCategory, 2284 'event_label': "CustomerTestimonialsPageNavLinkPlatinum1" 2285 }); 2286 }); 2287 $('#ServicesPageNavigationLink').on('click', function () { 2288 gtag('event', 'Link Click', { 2289 'event_category': analyticsCategory, 2290 'event_label': "CustomerServicesPageNavLinkPlatinum1" 2291 }); 2292 }); 2293 $('#AboutPageNavigationLink').on('click', function () { 2294 gtag('event', 'Link Click', { 2295 'event_category': analyticsCategory, 2296 'event_label': "CustomerAboutPageNavLinkPlatinum1" 2297 }); 2298 }); 2299 2300 $(document).on('click', '.btn-link', function() { 2301 // Find the closest parent that includes both the button and the items to be toggled 2302 var $parentNav = $(this).closest('.navbar-attribute'); 2303 2304 // Toggle the visibility of hidden items within this specific parent 2305 var $hiddenItems = $parentNav.find('.hidden-item'); 2306 $hiddenItems.toggle(); 2307 2308 // Find the arrow within the clicked button 2309 var $arrow = $(this).find('.arrow'); 2310 2311 // Toggle arrow direction by switching classes 2312 if ($arrow.hasClass('arrow-down')) { 2313 $arrow.removeClass('arrow-down').addClass('arrow-up'); 2314 } else { 2315 $arrow.removeClass('arrow-up').addClass('arrow-down'); 2316 } 2317 }); 2318 2319 $(document).on('click', '#photos-image-element1', function () { 2320 $('#selectedServiceId').val('service1'); 2321 DisplayPage('#servicesPage'); 2322 }); 2323 $(document).on('click', '#photos-image-element2', function () { 2324 $('#selectedServiceId').val('service2'); 2325 DisplayPage('#servicesPage'); 2326 }); 2327 $(document).on('click', '#photos-image-element3', function () { 2328 $('#selectedServiceId').val('service3'); 2329 DisplayPage('#servicesPage'); 2330 }); 2331 2332 //unable to directly track cross domain iFrame clicks. Track only first click on the iFrame itself 2333 var monitor = setInterval(function () { 2334 var elem = document.activeElement; 2335 if (elem && elem.tagName == "IFRAME" && elem.id == "googleMapEmbed") { 2336 gtag('event', 'iFrame Click', { 2337 'event_category': analyticsCategory, 2338 'event_label': "GoogleDirectionsPlatinum1" 2339 }); 2340 clearInterval(monitor); 2341 } 2342 }, 1000); 2343 2344 var navbarHeight = $('#headernav').outerHeight(); // Make sure this matches your navbar class 2345 document.documentElement.style.setProperty('--scroll-padding-top', navbarHeight + 'px'); 2346 });
2347 2348 function scrollToService(serviceId) { 2349 var targetElement = document.getElementById(serviceId); 2350 if (targetElement) { 2351 var headerHeight = document.querySelector('#headernav').offsetHeight; 2352 var descriptionOffset = 165; // Adjust this value as needed 2353 var scrollPosition = targetElement.offsetTop - headerHeight - descriptionOffset; 2354 2355 window.scrollTo({ 2356 top: scrollPosition, 2357 behavior: 'smooth' 2358 }); 2359 } else { 2360 console.log('Service section element not found'); 2361 } 2362 } 2363 2364 function DisplayPage(page, serviceId) { 2365 PauseVideoPlayer(); 2366 2367 if (!$(page).hasClass("show")) { 2368 $("#pages div.collapse.show").collapse("hide"); 2369 } 2370 $(page).collapse("show"); 2371 2372 if(page == "#homePage") 2373 { 2374 var carousel = $(".slick-slider"); 2375 if(carousel != null) 2376 { 2377 carousel.slick('refresh'); 2378 } 2379 } 2380 2381 if (page == "#testimonialsPage") { 2382 // Load Testimonials via Ajax if not already loaded 2383 if (!testimonialsRendered) { 2384 ShowLoading(); 2385 $.get('/Home/GetTestimonialsContent', { id: 40420, template: "Platinum_1"}, function (data) { 2386 if (data != "") 2387 { 2388 $("#testimonialsPage").append(data); 2389 } 2390 }).always(function() { 2391 HideLoading(); 2392 testimonialsRendered = true; 2393 }); 2394 } 2395 2396 $("html, body").animate({ scrollTop: 0 }, "fast"); 2397 } 2398 2399 if (page == "#aboutPage") { 2400 // Load About via Ajax if not already loaded 2401 if (!aboutRendered) { 2402 ShowLoading(); 2403 $.get('/Home/GetAboutContent', { id: 40420, template: "Platinum_1"}, function (data) { 2404 if (data != "") 2405 { 2406 $("#aboutPage").append(data); 2407 $.getScript("https://maps.googleapis.com/maps/api/js?key=AIzaSyB2UoPWVWPR8WrIFK_zqJ5nTN1NvGm9dqY&callback=initMap"); 2408 2409 } 2410 }).always(function() { 2411 HideLoading(); 2412 aboutRendered = true; 2413 }); 2414 } 2415 } 2416 2417 if (page === "#servicesPage") 2418 { 2419 // Load Services via Ajax if not already loaded 2420 if (!servicesRendered) { 2421 ShowLoading(); 2422 $.get('/Home/GetServicesContent', { id: 40420, template: "Platinum_1" }) 2423 .done(function (data) { 2424 if (data !== "") { 2425 $("#servicesPage").append(data); 2426 servicesRendered = true; 2427 // Trigger the scroll after a short delay 2428 setTimeout(function() { 2429 var selectedServiceId = $('#selectedServiceId').val(); 2430 if (selectedServiceId) { 2431 scrollToService(selectedServiceId); 2432 $('#selectedServiceId').val(''); 2433 } 2434 }, 300); // Adjust the delay as needed 2435 } 2436 }) 2437 .always(function () { 2438 HideLoading(); 2439 }); 2440 } else { 2441 // If the services are already rendered, trigger the scroll after a short delay 2442 setTimeout(function() { 2443 var selectedServiceId = $('#selectedServiceId').val(); 2444 if (selectedServiceId) { 2445 scrollToService(selectedServiceId); 2446 $('#selectedServiceId').val(''); 2447 } 2448 }, 100); // Adjust the delay as needed 2449 } 2450 } else { 2451 window.scrollTo(0, 0); 2452 } 2453 2454 if (page == "#imageryPage") { 2455 // Load photos via Ajax if not already loaded 2456 if (!photosRendered) { 2457 ShowLoading(); 2458 $.get('/Home/GetPhotosContent', { id: 40420, template: "Platinum_1"}, function (data) { 2459 if (data != "") 2460 { 2461 $("#imageryPage").append(data); 2462 } 2463 }).always(function() { 2464 HideLoading(); 2465 photosRendered = true; 2466 }); 2467 } 2468 } 2469 2470 if (page == "#contactPage") { 2471 // Load Contact via Ajax if not already loaded 2472 if (!contactRendered) { 2473 ShowLoading(); 2474 $.get('/Home/GetContactContent', { id: 40420, template: "Platinum_1"}, function (data) { 2475 if (data != "") 2476 { 2477 $("#contactPage").append(data); 2478 } 2479 }).always(function() { 2480 HideLoading(); 2481 contactRendered = true; 2482 }); 2483 } 2484 } 2485 2486 if (page == "#locationsPage") { 2487 // Load Locations via Ajax if not already loaded 2488 if (!locationsRendered) { 2489 ShowLoading(); 2490 2491 $.get('/Home/GetLocationsContent', { id: 40420, template: "Platinum_1" }, function (data) { 2492 if (data != "") 2493 { 2494 $("#locationsPage").append(data); 2495 } 2496 }).always(function() { 2497 HideLoading(); 2498 locationsRendered = true; 2499 }); 2500 } 2501 } 2502 2503 if (page == "#faqPage") { 2504 // Load Faqs via Ajax if not already loaded 2505 if (!faqRendered) { 2506 ShowLoading(); 2507 2508 $.get('/Home/GetFAQContent', { id: 40420, template: "Platinum_1" }, function (data) { 2509 if (data != "") { 2510 $("#faqPage").append(data); 2511 } 2512 }).always(function () { 2513 HideLoading(); 2514 faqRendered = true; 2515 }); 2516 } 2517 } 2518 2519 if (page == "#contentPage") { 2520 // Load Additional Content via Ajax if not already loaded 2521 if (!additionalContentRendered) { 2522 ShowLoading(); 2523 2524 $.get('/Home/GetAdditionalContentContent', { id: 40420, template: "Platinum_1" }, function (data) { 2525 if (data != "") { 2526 $("#contentPage").append(data); 2527 } 2528 }).always(function () { 2529 HideLoading(); 2530 additionalContentRendered = true; 2531 }); 2532 } 2533 } 2534 } 2535 2536 function PauseVideoPlayer() { 2537 2538 var videos = document.querySelectorAll('video'); 2539 var iframes = document.querySelectorAll('iframe'); 2540 2541 //mp4 2542 Array.prototype.forEach.call(videos, video => { 2543 video.pause(); 2544 }); 2545 2546 //youtube 2547 Array.prototype.forEach.call(iframes, iframe => { 2548 iframe.contentWindow.postMessage(JSON.stringify({ 2549 event: 'command', 2550 func: 'pauseVideo' 2551 }), '*'); 2552 }); 2553 2554 //wistia + vimeo 2555 Array.prototype.forEach.call(iframes, iframe => { 2556 iframe.contentWindow.postMessage(JSON.stringify({ 2557 "method": "pause", 2558 "value": "true" 2559 }), '*'); 2560 }); 2561 } 2562 2563 function GetPostsData(i) 2564 { 2565 if(i != 0){ 2566 ShowLoading(); 2567 } 2568 postsPageIndex = postsPageIndex + i; 2569 $.get('/Home/GetTestimonialPostsInfo', {id: 40420, program_id: 18, profileDomain: 'https://maryscleaningc
2569are.com/', subdomain: 'maryscleaningcare', testimonialSSL : 'False', domainSSL : 'True', pageIndex: postsPageIndex, pageSize: pageSize, file: '_TestimonialPostsContent'}, function (data) { 2570 if (data != "") 2571 { 2572 $(".postsInfo").html(data); 2573 } 2574 }).always(function() { 2575 HideLoading(); 2576 }); 2577 SetPostsPaginationButtons(); 2578 } 2579 2580 function GetPostsBanner() 2581 { 2582 ShowLoading(); 2583 var bannerObj = null; 2584 $.ajax({ 2585 method: "POST", 2586 url: "/Home/GetPostBanner", 2587 data:JSON.stringify( 2588 { 2589 banner: bannerObj, 2590 testimonialPageColor: (false) ? 'transparent' :'', 2591 postsColor: '#ffffff' 2592 }), 2593 contentType: 'application/json', 2594 success: function (data) { 2595 var holdaspect = ""; 2596 if ($("#homepagebanner").hasClass('aspect-ratio-16x9')) { 2597 $('#homepagebanner').removeClass('aspect-ratio-16x9'); 2598 holdaspect = "aspect-ratio-16x9"; 2599 } 2600 2601 if(isMobile || isTablet){ 2602 setTimeout(function(){ 2603 $(".home-content").prepend(data); 2604 }, 5000); 2605 } 2606 else{ 2607 $(".home-content").prepend(data); 2608 } 2609 2610 $('#bannerclose').click(function() { 2611 if (holdaspect != "") { 2612 $('#homepagebanner').addClass('aspect-ratio-16x9'); 2613 } 2614 }); 2615 2616 /*remove banner after 30 seconds regardless of mobile/tablet/desktop*/ 2617 if (!keepPostUp) { 2618 setTimeout(function () { 2619 $("#posts-banner-alert").remove(); 2620 if (holdaspect != "" && !(isMobile || isTablet)) { 2621 $('#homepagebanner').addClass('aspect-ratio-16x9'); 2622 } 2623 }, 30000); 2624 } 2625 } 2626 }).always(function() { 2627 HideLoading(); 2628 if (holdaspect != "" && !(isMobile || isTablet)) { 2629 $('#homepagebanner').addClass('aspect-ratio-16x9'); 2630 } 2631 }); 2632 } 2633 2634 function SetPostsPaginationButtons() 2635 { 2636 //look ahead by one 2637 if (((postsPageIndex + 1) * pageSize) >= numOfPosts) { 2638 $("#nextPosts").hide(); 2639 } 2640 else { 2641 $("#nextPosts").show(); 2642 $("#nextPosts").prop('disabled', false); 2643 } 2644 2645 if (postsPageIndex <= 0) { 2646 postsPageIndex = 0; 2647 $("#prevPosts").hide(); 2648 } 2649 else if (postsPageIndex > 0) { 2650 $("#prevPosts").show(); 2651 $("#prevPosts").prop('disabled', false); 2652 } 2653 } 2654 2655 function ShowLoading() 2656 { 2657 $("#loadingImg").show(); 2658 } 2659 2660 function HideLoading() 2661 { 2662 $("#loadingImg").hide(); 2663 } 2664 2665 function checkMobile() { 2666 var windowsize = $(window).width(); 2667 switch (true) { 2668 case /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent): 2669 isMobile = true; 2670 break; 2671 case windowsize < 800: 2672 isMobile = true; 2673 break; 2674 default: 2675 isMobile = false; 2676 } 2677 } 2678 2679 function checkTablet() { 2680 var windowsize = $(window).width(); 2681 switch (true) { 2682 case (windowsize >= 800) && (windowsize < 1200): 2683 isTablet = true; 2684 break; 2685 default: 2686 isTablet = false; 2687 } 2688 } 2689 2690 function checkHashRoute() { 2691 if ((location.hash != null) && (location.hash != "") && (location.hash == '#homePage' || location.hash == '#aboutPage' || location.hash == '#servicesPage' || location.hash == '#testimonialsPage' || location.hash == '#contactPage' || location.hash == '#locationsPage' || location.hash == '#faqPage' || location.hash == '#contentPage')) { 2692 DisplayPage(location.hash); 2693 } 2694 } 2695 2696 function contactUsPost(event) { 2697 var _contactForm = $("#ContactPostForm"); 2698 2699 // Clear previous validation states 2700 $('.validation-error-message').remove(); 2701 _contactForm.find('.validation-error').removeClass('validation-error'); 2702 $("#contactResponseMessage").empty(); 2703 2704 if (_contactForm[0].checkValidity()) { 2705 contactUsSubmit(event); 2706 } else { 2707 var firstInvalidField = null; 2708 2709 _contactForm.find('input[required], textarea[required], select[required]').each(function () { 2710 var $field = $(this); 2711 2712 if (!this.checkValidity()) { 2713 var fieldId = $field.attr('id') || $field.attr('name'); 2714 var fieldLabel = getFieldLabel(fieldId); 2715 2716 $field.addClass('validation-error'); 2717 2718 var errorMessage = getCustomErrorMessage($field, fieldLabel); 2719 $field.after(`<div class="validation-error-message">${errorMessage}</div>`); 2720 2721 if (!firstInvalidField) { 2722 firstInvalidField = $field; 2723 } 2724 } 2725 }); 2726 2727 // Focus first invalid field 2728 if (firstInvalidField) { 2729 firstInvalidField.focus(); 2730 } 2731 2732 // Re-enable submit button 2733 $("#btnContactPost").prop('disabled', false); 2734 $("#btnContactPost").css('font-weight', 'bold'); 2735 } 2736 } 2737 2738 function getFieldLabel(fieldId) { 2739 var labels = { 2740 'contact_us_phone': 'Phone Number', 2741 'contact_us_address': 'Address', 2742 'contact_us_city': 'City', 2743 'contact_us_state': 'State', 2744 'contact_us_zip': 'ZIP Code', 2745 'contact_us_subject': 'Subject', 2746 'contact_us_name': 'Name', 2747 'contact_us_email': 'Email', 2748 'contact_us_message': 'Message' 2749 }; 2750 return labels[fieldId] || 'This field'; 2751 } 2752 2753 function getCustomErrorMessage($field, fieldLabel) { 2754 if ($field.prop('required') && !$field.val().trim()) { 2755 return `${fieldLabel} required`; 2756 } 2757 2758 if ($field.attr('type') === 'email' && $field.val()) { 2759 return `Please enter a valid email address.`; 2760 } 2761 2762 return `Please enter a valid ${fieldLabel.toLowerCase()}.`; 2763 } 2764 2765 function contactUsSubmit(event) { 2766 event.preventDefault(); 2767 2768 var form = $("#ContactPostForm"); 2769 grecaptcha.ready(function() { 2770 grecaptcha.execute('6LcokacUAAAAAH_snk3O5bpdYwjpC5XAuXNLrJLH', {action: 'contact_us'}).then(function(token) { 2771 $("#g-Recaptcha-Response-Contact").attr("value", token); //add token for server side validation 2772 $.ajax({
2773 type: "POST", 2774 url: "/Home/ContactPost", 2775 data: form.serialize(), 2776 success: function(data){ 2777 if (data.indexOf('{"message":') != -1) { 2778 var jsonData = JSON.parse(data); 2779 if (!jsonData.success) { 2780 $("#contactResponseMessage").html('<div class="alert alert-warning alert-dissmisable" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>' + jsonData.message + '</div>'); 2781 recaptchaContactUsInComplete(); 2782 showClassicCaptcha("contact"); 2783 } 2784 else{ 2785 $("#contactResponseMessage").html('<div class="alert alert-success alert-dissmisable" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>' + jsonData.message + '</div>'); 2786 recaptchaContactUsComplete(); 2787 } 2788 } 2789 } 2790 }); 2791 }); 2792 }); 2793 } 2794 2795 function ScrollForPosts(e){ 2796 // target element 2797 var $id = $(".posts-partial-content"); 2798 if ($id.length === 0) { 2799 return; 2800 } 2801 2802 // prevent standard hash navigation (avoid blinking in IE) 2803 e.preventDefault(); 2804 2805 // top position relative to the document 2806 var pos = $id.offset().top - $('.navbar').height(); 2807 2808 // animated top scrolling 2809 // .stop() keeps from scrolling to top then to element 2810 $('body, html').stop().animate({ scrollTop: pos }, 500); 2811 } 2812 2813 function recaptchaContactUsInComplete(){ 2814 $("#btnContactPost").prop('disabled', false); 2815 $("#btnContactPost").css('font-weight', 'bold'); 2816 } 2817 2818 function recaptchaContactUsComplete() { 2819 $("#btnContactPost").prop('disabled', false); 2820 $("#btnContactPost").css('font-weight', 'normal'); 2821 $("#ContactPostForm").trigger("reset"); 2822 } 2823 2824 function recaptchaReviewIncomplete(){ 2825 $("#postReview").prop('disabled', false); 2826 $("#postReview").css('font-weight', 'bold'); 2827 } 2828 2829 function recaptchaReviewComplete(){ 2830 $("#postReview").css('font-weight', 'normal'); 2831 $("#postReviewForm").trigger("reset"); 2832 $("#postReview").prop('disabled', false); 2833 } 2834 2835 function ClearReviewFields() { 2836 $('#starRating').barrating('clear'); 2837 $("#review").val(""); 2838 $("#review_alias").val(""); 2839 $("#review_heading").val(""); 2840 $("#reviewCaptcha").val(""); 2841 } 2842 2843 function resetReviewButton($button) { 2844 var $buttonText = $button.find('.button-text'); 2845 var $spinner = $button.find('.spinner'); 2846 2847 $button.prop('disabled', false); 2848 2849 if ($buttonText.length && $spinner.length) { 2850 $buttonText.show(); 2851 $spinner.hide(); 2852 } else { 2853 var originalText = $button.data('original-text'); 2854 if (originalText) { 2855 $button.html(originalText); 2856 } 2857 } 2858 } 2859 2860 function setProcessingState($button, $form, isProcessing) { 2861 var $buttonText = $button.find('.button-text'); 2862 var $spinner = $button.find('.spinner'); 2863 2864 if (isProcessing) { 2865 $button.prop('disabled', true); 2866 $form.addClass('review-form-processing'); 2867 $form.find('input, textarea, select').not('#profile_id, #g-Recaptcha-Response-Review, [type="hidden"]').prop('disabled', true); 2868 2869 if ($buttonText.length && $spinner.length) { 2870 $buttonText.hide(); 2871 $spinner.show(); 2872 } else { 2873 $button.data('original-text', $button.html()); 2874 $button.html('<i class="fas fa-spinner fa-spin"></i> Processing...'); 2875 } 2876 } else { 2877 $button.prop('disabled', false); 2878 $form.removeClass('review-form-processing'); 2879 $form.find('input, textarea, select').prop('disabled', false); 2880 2881 if ($buttonText.length && $spinner.length) { 2882 $buttonText.show(); 2883 $spinner.hide(); 2884 } else { 2885 var originalText = $button.data('original-text'); 2886 if (originalText) { 2887 $button.html(originalText); 2888 } 2889 } 2890 } 2891 } 2892 2893 function ClearReviewFields() { 2894 console.log('Clearing review fields'); 2895 $('#starRating').barrating('clear'); 2896 $("#review").val(""); 2897 $("#review_alias").val(""); 2898 $("#review_heading").val(""); 2899 $("#reviewCaptcha").val(""); 2900 2901 $("#postReviewForm input, #postReviewForm textarea").removeClass('error'); 2902 $("#postReviewForm .br-widget").removeClass('error'); 2903 } 2904 2905 function validateReview(btn) { 2906 var $form = $("#postReviewForm"); 2907 var isValid = true; 2908 2909 console.log('Starting validation...'); 2910 2911 $form.find('input, textarea').removeClass('error'); 2912 $form.find('.br-widget').removeClass('error'); 2913 2914 var reviewText = $('#review').val(); 2915 console.log('Review text length:', reviewText ? reviewText.trim().length : 0); 2916 2917 if (!reviewText || reviewText.trim().length === 0) { 2918 console.log('Review text validation failed - empty or whitespace only'); 2919 $('#review').addClass('error'); 2920 $('#review').focus(); 2921 isValid = false; 2922 } else { 2923 console.log('Review text validation passed'); 2924 } 2925 2926 var rating = null; 2927 var selectRating = $('#starRating')[0] ? $('#starRating')[0].value : null; 2928 var jqueryRating = $('#starRating').val(); 2929 var selectedOption = $('#starRating option:selected').val(); 2930 var barRating = null; 2931 2932 if (typeof $('#starRating').barrating === 'function') { 2933 try { 2934 var barResult = $('#starRating').barrating('show'); 2935 if (barResult && typeof barResult === 'string') { 2936 barRating = barResult; 2937 } else if (barResult && typeof barResult === 'number') { 2938 barRating = barResult.toString(); 2939 } 2940 } catch (e) { 2941 console.log('Error getting barrating value:', e); 2942 } 2943 } 2944 2945 if (jqueryRating && typeof jqueryRating === 'string') { 2946 } else if (jqueryRating && typeof jqueryRating === 'number') { 2947 jqueryRating = jqueryRating.toString(); 2948 } else { 2949 jqueryRating = null; 2950 } 2951 2952 if (barRating && barRating !== "" && barRating !== "0") { 2953 rating = barRating; 2954 } else if (selectRating && selectRating !== "" && selectRating !== "0") { 2955 rating = selectRating; 2956 } else if (selectedOption && selectedOption !== "" && selectedOption !== "0") { 2957 rating = selectedOption; 2958 } else if (jqueryRating && jqueryRating !== "" && jqueryRating !== "0") { 2959 rating = jqueryRating; 2960 } 2961 2962 if (rating && typeof rating !== 'string') { 2963 console.log('WARNING: Rating is not a string, converting:', rating, typeof rating);
2964 rating = String(rating); 2965 } 2966 2967 if (rating && typeof rating === 'object') { 2968 console.error('CRITICAL ERROR: Rating is still an object!', rating); 2969 rating = null; 2970 } 2971 2972 console.log('Validation - Final rating value:', rating, typeof rating); 2973 2974 if (!rating || rating === "" || rating === "0") { 2975 console.log('Rating validation failed'); 2976 $('.br-widget').addClass('error'); 2977 isValid = false; 2978 } else { 2979 console.log('Rating validation passed with value:', rating); 2980 } 2981 2982 console.log('Validation result:', isValid); 2983 return isValid; 2984 } 2985 2986 function validateReviewLength() { 2987 var text = $('#review').val(); 2988 var success = true; 2989 2990 if (text.trim().length == 0) { 2991 $('#review').addClass('error'); 2992 success = false; 2993 } 2994 return success; 2995 } 2996 2997 function validateRating() { 2998 var rating = null; 2999 3000 // Use the same rating detection logic 3001 var selectRating = $('#starRating')[0] ? $('#starRating')[0].value : null; 3002 var jqueryRating = $('#starRating').val(); 3003 var selectedOption = $('#starRating option:selected').val(); 3004 var barRating = null; 3005 3006 if (typeof $('#starRating').barrating === 'function') { 3007 try { 3008 barRating = $('#starRating').barrating('show'); 3009 } catch (e) { 3010 console.log('Error getting barrating value in validateRating:', e); 3011 } 3012 } 3013 3014 // Determine which rating to use 3015 if (barRating && barRating !== "" && barRating !== "0") { 3016 rating = barRating; 3017 } else if (selectRating && selectRating !== "" && selectRating !== "0") { 3018 rating = selectRating; 3019 } else if (selectedOption && selectedOption !== "" && selectedOption !== "0") { 3020 rating = selectedOption; 3021 } else if (typeof jqueryRating === 'string' && jqueryRating !== "" && jqueryRating !== "0") { 3022 rating = jqueryRating; 3023 } 3024 3025 var success = true; 3026 if (!rating || rating === "" || rating === "0") { 3027 $('.br-widget').addClass('error'); 3028 success = false; 3029 } 3030 return success; 3031 } 3032</script>
3032 3033</body> 3034</html>
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.