PageSourceSearch

https://recallhq.com/assets/js/script.js?12ab123

js recallhq.com collected 2026-10-09 22:39:48 UTC 18,849 bytes, 695 lines download raw bytes

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.