1console.log("script connected") 2import { CountUp } from './countUp.js-master/dist/countUp.js'; 3 4 5// Set Colors 6let primary_color_700 = getComputedStyle(document.body).getPropertyValue('--primary-700').trim() 7let primary_color_600 = getComputedStyle(document.body).getPropertyValue('--primary-600').trim() 8 9 10 11// Launcg Shedule Demo 12function checkScheduleDemo() { 13 // Parse the URL and extract query parameters 14 const urlParams = new URLSearchParams(window.location.search); 15 16 // Check if the 'schedule_demo' parameter is set to '1' 17 if (urlParams.get('schedule_demo') === '1') { 18 // Find the element with the class name 'openModalBtn' 19 const modalButton = document.querySelector('.openModalBtn'); 20 21 // Check if the element exists before trying to click it 22 if (modalButton) { 23 modalButton.click(); 24 25 // Remove the 'schedule_demo' query parameter from the URL 26 urlParams.delete('schedule_demo'); 27 28 // Update the browser's address bar without reloading the page 29 const newUrl = `${window.location.origin}${window.location.pathname}?${urlParams.toString()}`; 30 window.history.replaceState(null, '', newUrl); 31 } else { 32 console.warn("No element with class 'openModalBtn' found."); 33 } 34 } 35} 36 37// Run the function after the DOM is fully loaded 38document.addEventListener('DOMContentLoaded', checkScheduleDemo); 39 40 41 42function handleChatQuery() { 43 const urlParams = new URLSearchParams(window.location.search); 44 if (urlParams.get('chat') === '1') { 45 // Wait for 2 seconds 46 setTimeout(() => { 47 const chatElement = document.getElementById('launch-chat'); 48 if (chatElement) { 49 chatElement.click(); // Simulate the click 50 // Remove the query parameter from the URL 51 urlParams.delete('chat'); 52 const newUrl = `${window.location.origin}${window.location.pathname}?${urlParams.toString()}`; 53 window.history.replaceState({}, document.title, newUrl); 54 } else { 55 console.warn('Element with ID #launch-chat not found.'); 56 } 57 }, 1500); // 2 seconds delay 58 } 59} 60 61// Run the function when the page loads 62document.addEventListener('DOMContentLoaded', handleChatQuery); 63 64 65 66 67 68 69 70// Logos 71 72// Select all elements you want to attach the listener to 73const elements = document.querySelectorAll('.logo-wrapper'); 74 75// Add event listener to each element 76elements.forEach(element => { 77 element.addEventListener('mouseenter', () => { 78 element.querySelector('.grey').style.display = "none" 79 element.querySelector('.color').style.display = "block" 80 }); 81 element.addEventListener('mouseleave', () => { 82 element.querySelector('.grey').style.display = "block" 83 element.querySelector('.color').style.display = "none" 84 }); 85 86}); 87 88 89/////////////////////////////////////////////////////////// 90////////////////////////// Confirm Market Count Modal ///// 91/////////////////////////////////////////////////////////// 92 93// Get all elements with the class 'openModalBtn' 94const openConfirmModalBtns = document.querySelectorAll('.openConfirmModalBtn'); 95const confirmModal = document.getElementById('confirm-modal'); 96const closeConfirmModalBtn = document.getElementById('closeConfirmModalBtn'); 97 98// Function to open the modal 99function openConfirmModal() { 100 confirmModal.style.display = 'flex'; 101 102 document.querySelector('[data-dealer-name]').innerHTML = document.querySelector('#dealer_name').value 103 104 document.querySelector('[data-brands]').innerHTML = gatherMakes() 105} 106 107// Gether makes selected in market count request 108function gatherMakes() { 109 let makes_selected = document.getElementsByName('selected') 110 let brands = [] 111 makes_selected.forEach(inputElem => { 112 if (inputElem.checked == true) { 113 brands.push(inputElem.value) 114 } 115 }) 116 return brands.toString() 117} 118 119// Function to close the modal 120function closeConfirmModal() { 121 confirmModal.style.display = 'none'; 122} 123 124// Attach event listeners to all 'openModalBtn' elements 125openConfirmModalBtns.forEach((btn) => { 126 btn.addEventListener('click', openConfirmModal); 127}); 128 129// Attach event listener to the close button 130closeConfirmModalBtn.addEventListener('click', closeConfirmModal); 131 132 133 134// Metrics Section Count Up 135function countUp(elem, target, decimals) { 136 137 const options = { 138 useGrouping: false, 139 decimalPlaces: decimals, 140 }; 141 let avg_ro = new CountUp(elem, target, options); 142 if (!avg_ro.error) { 143 avg_ro.start(); 144 } else { 145 console.error(avg_ro.error); 146 } 147 148 window.onload = function() { 149 var countUp = new CountUp('target', 2500); 150 countUp.start(); 151 } 152 153} 154 155// Trigger Function Based on Element Visibility 156function triggerOnVisibility(elementId, callback, threshold = 0.5) { 157 const targetElement = document.getElementById(elementId); 158 159 if (!targetElement) { 160 console.error(`Element with ID "${elementId}" not found.`); 161 return; 162 } 163 164 const observer = new IntersectionObserver( 165 ([entry], observerInstance) => { 166 if (entry.isIntersecting) { 167 callback(entry.target); // Trigger the callback with the element 168 observerInstance.unobserve(entry.target); // Stop observing after first visibility 169 } 170 }, 171 { threshold } // Visibility threshold (default 50% visible) 172 ); 173 174 observer.observe(targetElement); 175} 176 177// Count Up When Visible 178document.addEventListener("DOMContentLoaded", () => { 179 triggerOnVisibility("metrics-section", (element) => { 180 countUp('avg_ro', 564,0) 181 countUp('avg_roi', 10.7,1) 182 countUp('pct_customer_pay', 70,0) 183 }); 184}); 185 186 187 188function openCrispChat() { 189 // If Crisp is ready, open it 190 if (window.$crisp && window.$crisp.push) { 191 window.$crisp.push(["do", "chat:open"]); 192 return true; 193 } 194 return false;
195} 196 197function attachChatLaunchListeners() { 198 document.querySelectorAll("[data-launch-chat]").forEach((el) => { 199 el.addEventListener("click", (event) => { 200 event.preventDefault(); 201 202 // Try immediately 203 if (openCrispChat()) return; 204 205 // If not ready yet, poll briefly until Crisp loads 206 const start = Date.now(); 207 const timer = setInterval(() => { 208 if (openCrispChat() || Date.now() - start > 5000) { 209 clearInterval(timer); 210 } 211 }, 100); 212 }); 213 }); 214} 215 216document.addEventListener("DOMContentLoaded", attachChatLaunchListeners); 217 218 219//////////////////////////////////////////////////////// 220////////////////////////// DEALER AUTOFILL////////////// 221//////////////////////////////////////////////////////// 222 223 224google.maps.event.addDomListener(window, 'load', initialize); 225 226function initialize() { 227 228 var input = document.getElementById('dealer_name'); 229 const options = { 230 types: ['car_dealer'], 231 componentRestrictions: { country: "us" }, 232 enablePoweredByContainer: false 233 } 234 235 var autocomplete = new google.maps.places.Autocomplete(input, options); 236 237 autocomplete.addListener('place_changed', function () { 238 239 var place = autocomplete.getPlace(); 240 241 242 243 $('#dealer_name').val(place.name); 244 $('#dealer_address').html(place.adr_address); 245 246 let street_address = $('#dealer_address > span.street-address').html() 247 let city = $('#dealer_address > span.locality').html() 248 let state = $('#dealer_address > span.region').html() 249 let zipcode = $('#dealer_address > span.postal-code').html() 250 251 252 $('#dealer_street_address').val(street_address); 253 $('#dealer_city').val(city); 254 $('#dealer_state').val(state); 255 $('#dealer_zipcode').val(zipcode); 256 $('#dealer_latitude').val(place.geometry['location'].lat().toFixed(6)); 257 $('#dealer_longitude').val(place.geometry['location'].lng().toFixed(6)); 258 $('#dealer_place_id').val(place.place_id); 259 $('#google_place_id').prop("value",place.place_id); 260 $('#dealer_rating').val(place.rating); 261 $('#dealer_rating_total').val(place.user_ratings_total); 262 263 if (place.website != null && place.website != "") { 264 let dealer_webiste = place.website.split("?")[0] 265 $('#dealer_website').val(dealer_webiste) 266 } 267 268 validateDealer() 269 updateMap() 270 271 272 273}); 274 275} 276 277 278//////////////////////////////////////////////////////// 279////////////////////////// MAPPING ///////////////////// 280//////////////////////////////////////////////////////// 281 282// Set your Mapbox access token 283mapboxgl.accessToken = 'pk.eyJ1IjoiYmNraW5jYWlkIiwiYSI6ImNsOWVuOHI3bDAxZGc0Mm14bjZ2OGlkcTUifQ.hlNSVNNNjA9UJ3hNT3Icsw'; 284 285 286// Initialize the map 287const map = new mapboxgl.Map({ 288 container: 'map', // ID of the container element 289 style: 'mapbox://styles/mapbox/streets-v11', // Map style 290 center: [-98.5795, 39.8283], // Center at the middle of the United States 291 zoom: 2.7, // Zoom level to see the entire US 292 maxZoom: 9 293}); 294 295// Function to add an image marker to the map 296function addImageMarker(lng, lat, imageUrl, radiusInMiles = 20) { 297 298 deleteElementsByClassName('dealer-point') 299 300 const imageMarker = document.createElement('img'); 301 imageMarker.classList.add('dealer-point') 302 imageMarker.src = imageUrl; 303 // imageMarker.style.width = '50px'; 304 imageMarker.style.height = '50px'; 305 imageMarker.style.cursor = 'pointer'; 306 307 // Create a marker and add it to the map 308 new mapboxgl.Marker({ element: imageMarker }) 309 .setLngLat([lng, lat]) // Set marker location 310 .addTo(map); 311} 312 313// Function to add a radius circle 314function addRadius(lng, lat, radius=20) { 315 316 var options = { 317 steps: 100, 318 units: 'miles' 319 }; 320 var center = [lng, lat]; 321 var circle = turf.circle(center, radius, options); 322 323 map.addLayer({ 324 "id": "circle-fill", 325 "type": "fill", 326 "source": { 327 "type": "geojson", 328 "data": circle 329 }, 330 "paint": { 331 "fill-color": primary_color_700, 332 "fill-opacity": 0.1 333 } 334 }); 335 336 map.addLayer({ 337 "id": "circle-outline", 338 "type": "line", 339 "source": { 340 "type": "geojson", 341 "data": circle 342 }, 343 "paint": { 344 "line-color": primary_color_700, 345 "line-opacity": 1, 346 "line-width": 1, 347 "line-offset": 0 348 }, 349 "layout": { 350 351 } 352 }); 353} 354 355// Set Plot Data 356function setPointSize(vin_count) { 357 if (vin_count < 50) return "vc-xxs" 358 if (vin_count < 100) return "vc-xs" 359 if (vin_count < 250) return "vc-sm" 360 if (vin_count < 500) return "vc-md" 361 if (vin_count < 750) return "vc-lg" 362 if (vin_count < 1000) return "vc-xl" 363 if (vin_count >= 1000) return "vc-xxl" 364} 365 366// Get market count plat data 367async function getPlotData(data) { 368 const result = await $.ajax({ 369 url: 'assets/scripts/ajax/get_geo_data.php', 370 type: "POST", 371 data:{"data": data} 372 }) 373 374 let plot_data = JSON.parse(result) 375 376 if (plot_data.success == 0) { 377 378 return 0 379 } 380 381 let output = { 382 "count_id": plot_data.count_id, 383 "plot_data": plot_data.plot_data 384 } 385 386 return output 387} 388 389// Create random wait times for plotting of elements 390function setRandomWait(callback) { 391 // Generate a random time between 50ms and 1000ms 392 const waitTime = Math.floor(Math.random() * (4000 - 500 + 1)) + 250; 393 394 // Use setTimeout to execute the callback after the random time 395 setTimeout(() => { 396 callback(waitTime); // Optionally pass the wait time to the callback 397 }, waitTime); 398} 399 400// Create plot markers 401function createPlotMarkers(map_data) { 402 403 // Load 404 const geojson = { 405 'type': 'FeatureCollection', 406 'features': map_data.plot_data 407 } 408 409 // add markers to map 410 for (const feature of geojson.features) { 411 412 setRandomWait((waitTime) => { 413 414 try { 415 // create a HTML element for each feature 416 const el = document.createElement('div') 417 // const el_shape = el.createElement('div') 418 // el_shape.className = 'point-shape' 419 420 el.className = 'point ' + setPointSize(feature.properties.vin_count) 421 422 // make a marker for each feature and add it to the map 423 new mapboxgl.Marker(el) 424 .setLngLat(feature.geometry.coordinates) 425 .setPopup( 426 new mapboxgl.Popup({ 427 offset: 25,
428 closeButton: false, 429 closeOnClick: true 430 }) 431 ) 432 .addTo(map); 433 } 434 catch(err) { 435 console.log(err.message) 436 } 437 438 }); 439 440 } 441} 442 443// Add plot data and markers to map 444async function plotData() { 445 446 let count_inputs = { 447 "dealer_name" : document.querySelector("#dealer_name").value.trim(), 448 "zipcode" : document.querySelector("#dealer_zipcode").value.trim().slice(0, 5), 449 "makes" : gatherMakes() 450 } 451 452 count_inputs = JSON.stringify(count_inputs) 453 console.log(count_inputs) 454 455 let plot_data = await getPlotData(count_inputs) 456 457 console.log(plot_data) 458 459 document.querySelector('#count_id').value = plot_data.count_id 460 461 createPlotMarkers(plot_data) 462 463 464} 465 466 467 468// Delete all elements with provided class name 469function deleteElementsByClassName(className) { 470 // Select all elements with the given class name 471 const elements = document.querySelectorAll(`.${className}`); 472 473 // Loop through the NodeList and remove each element from the DOM 474 elements.forEach(element => { 475 element.remove(); 476 }); 477} 478 479// Remove existing circle layers 480function removeCircleLayers(map) { 481 const layers = ["circle-fill", "circle-outline"]; 482 483 layers.forEach(layerId => { 484 if (map.getLayer(layerId)) { 485 const sourceId = map.getLayer(layerId).source; 486 map.removeLayer(layerId); // Remove the layer 487 if (map.getSource(sourceId)) { 488 map.removeSource(sourceId); // Remove the source 489 } 490 } 491 }); 492} 493 494// Update Map and Fly To 495function updateMap() { 496 497 let lon = document.querySelector("#dealer_longitude").value 498 let lat = document.querySelector("#dealer_latitude").value 499 500 const newCoordinates = [lon, lat]; // Example: San Francisco, CA 501 const imageUrl = 'https://recallhq.com/assets/images/recallhq_icon_location_marker.png'; // Example image URL 502 503 // Fly to the new coordinates 504 map.flyTo({ 505 center: newCoordinates, 506 zoom: 8, // Closer zoom level for the new location 507 speed: 1.5 // Animation speed 508 }); 509 510 511 // Add an image marker at the new location 512 addImageMarker(newCoordinates[0], newCoordinates[1], imageUrl); 513 removeCircleLayers(map) 514 515}; 516 517 518 519 520////////////////////////////////////////////////////////// 521///////// Advance Market Count Pages ///////////////////// 522////////////////////////////////////////////////////////// 523 524// Dealer Entry Proceed 525document.querySelector('#dealer-entry-proceed').addEventListener('click', function(e) { 526 e.preventDefault() 527 528 console.log("find out - add radius") 529 530 if (validateDealer() == 0) { 531 return 532 } 533 534 535 // Hide current page 536 document.querySelector('#dealer-entry').style.display = "none" 537 // Show next page 538 document.querySelector('#brands-entry').style.display = "flex" 539 540 541 let lon = document.querySelector("#dealer_longitude").value 542 let lat = document.querySelector("#dealer_latitude").value 543 544 // Add radius to new location 545 addRadius(lon, lat, 25); 546 547}) 548 549// Brands Entry Proceed 550document.querySelector('#back-to-dealer').addEventListener('click', function(e) { 551 e.preventDefault() 552 553 // Hide current page 554 document.querySelector('#brands-entry').style.display = "none" 555 // Show next page 556 document.querySelector('#dealer-entry').style.display = "flex" 557 558}) 559 560// Prompt confirm selections modal 561document.querySelector('#brands-entry-proceed').addEventListener('click', function(e) { 562 e.preventDefault() 563 564 if (validateBrands() == 0) { 565 return 566 } 567 568 // Open Confirm Modal 569 openConfirmModal() 570 571}) 572 573// Selections confirmed, proceed to contact form 574document.querySelector('#count-comfirmed').addEventListener('click', function(e) { 575 e.preventDefault() 576 577 console.log("almost there - add plot") 578 579 // Close Confirm Modal 580 closeConfirmModal() 581 582 // Hide current page 583 document.querySelector('#brands-entry').style.display = "none" 584 // Show next page 585 document.querySelector('#contact-entry').style.display = "flex" 586 587 // Focus on map 588 scrollToDivIfSmallScreen("map_wrapper") 589 590 // Show legend 591 document.querySelector('.legend').style.display = "flex" 592 593 // Plot Data 594 plotData() 595 596 597 598}) 599 600// document.querySelector("#capture_map").addEventListener("click", function(e) { 601// e.preventDefault() 602 603// console.log("save map") 604// captureMap() 605 606// }) 607 608 609 610// Contact Entry Proceed 611// document.querySelector('[data-market-count-submit]').addEventListener('click', function(e) { 612// e.preventDefault() 613 614// console.log("lets go - display receipt") 615// // Hide current page 616// document.querySelector('#contact-entry').style.display = "none" 617// // Show next page 618// document.querySelector('#count-submitted').style.display = "flex" 619 620// }) 621 622 623// Validate dealer entry 624function validateDealer() { 625 626 document.querySelector('#dealer_name').classList.remove('error') 627 document.querySelector('#dealer-help-text').style.display = "none" 628 629 let dealer_name = document.querySelector('#dealer_name').value 630 let dealer_latitude = document.querySelector('#dealer_latitude').value 631 let dealer_longitude = document.querySelector('#dealer_longitude').value 632 let dealer_place_id = document.querySelector('#dealer_place_id').value 633 634 let validated = 1 635 636 if ( 637 dealer_name == null || dealer_name == "" 638 || dealer_latitude == null || dealer_latitude == "" 639 || dealer_longitude == null || dealer_longitude == "" 640 || dealer_place_id == null || dealer_place_id == "" 641 ) { 642 validated = 0 643 644 console.log("Dealer Validation Failed") 645 document.querySelector('#dealer_name').classList.add('error') 646 document.querySelector('#dealer-help-text').style.display = "block" 647 648 } 649 650 return validated 651 652} 653 654// Validate brands entry 655function validateBrands() { 656 657 document.querySelector('#brands-input-wrapper').classList.remove('error') 658 document.querySelector('#brands-help-text').style.display = "none" 659
660 let brands = gatherMakes() 661 662 let validated = 1 663 664 if ( 665 brands == null || brands == "" 666 ) { 667 validated = 0 668 669 console.log("Brands Validation Failed") 670 document.querySelector('#brands-input-wrapper').classList.add('error') 671 document.querySelector('#brands-help-text').style.display = "block" 672 673 } 674 675 return validated 676 677} 678 679 680// Scroll to target 681function scrollToDivIfSmallScreen(divId) { 682 if (window.innerWidth < 500) { // Check if the screen width is less than 500px 683 const element = document.getElementById(divId); 684 if (element) { 685 element.scrollIntoView({ 686 behavior: 'smooth', 687 block: 'start', 688 }); 689 } else { 690 console.error(`No element found with ID: ${divId}`); 691 } 692 } else { 693 console.log('Screen width is greater than or equal to 500px. No scrolling performed.'); 694 } 695}
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.