"use strict"; // Variables for global usage let map; const initialZoom = 15; const targetZoom = 17.8; let salesCenterLat = 33.658600; let salesCenterLng = -78.983632; const zLevelHide = initialZoom; const zoomLevelToRotate = 19; const mapRotateDegrees = 90; let disableClicks = false; let lotAndFloorplanData = {}; const baseUrl = 'https://www.sayebrook.com'; const theme = 'sayebrook'; const markerIconURL = `/themes/sayebrook/modules/siteplan/images/sb-marker.svg`; const markerHideZoom = initialZoom; let markerArray = []; let infoWindows = []; const animationDelay = 1000; const cookieName = "mapZoomPlayed"; let zoomLevel = initialZoom; let sitePlanOverlay; function initMap() { const centerLat = 33.658600; const centerLng = -78.983632; const mapCenter = new google.maps.LatLng(centerLat, centerLng); const siteSVG = document.querySelector('#siteplanSVG'); const siteMapBounds = new google.maps.LatLngBounds( new google.maps.LatLng(33.646681, -79.003506), new google.maps.LatLng(33.674156, -78.962055) ); const mapOptions = { zoom: initialZoom, center: mapCenter, scrollwheel: true, mapTypeControl: false, fullscreenControl: true, fullscreenControlOptions: { position: google.maps.ControlPosition.RIGHT_BOTTOM, }, streetViewControl: false, controlSize: 30, mapId: '58bb8e82c39f0270' }; ///'ae63ecc734886ff6' map = new google.maps.Map(document.getElementById('map_canvas_full'), mapOptions); class sitePlanSVGOverlay extends google.maps.OverlayView { constructor(bounds, svg, map) { super(); this.bounds = bounds; this.svg = svg; this.map = map; this.setMap(map); } onAdd() { const panes = this.getPanes(); panes.overlayLayer.appendChild(this.svg); this.svg.style.display = 'block'; this.svg.style.position = 'absolute'; this.svg.style.zIndex = 1; this.attachEvents(); } attachEvents() { const lotElements = this.svg.querySelectorAll('[id*="Lot"]'); lotElements.forEach(el => { el.addEventListener('click', (e) => { handleLotClick(e, el); }); el.addEventListener('mouseover', (e) => { handleLotMouseOver(e, el); }); el.addEventListener('mouseout', (e) => { handleLotMouseOut(e, el); }); // Optional: Update tooltip position on mouse move el.addEventListener('mousemove', (e) => { updateTooltipPosition(e); }); }); } draw() { const projection = this.getProjection(); const sw = projection.fromLatLngToDivPixel(this.bounds.getSouthWest()); const ne = projection.fromLatLngToDivPixel(this.bounds.getNorthEast()); Object.assign(this.svg.style, { left: `${sw.x}px`, top: `${ne.y}px`, width: `${ne.x - sw.x}px`, height: `${sw.y - ne.y}px`, }); } show() { this.svg.style.display = 'block'; } hide() { this.svg.style.display = 'none'; } } sitePlanOverlay = new sitePlanSVGOverlay(siteMapBounds, siteSVG, map); handleMapEvents(map, sitePlanOverlay); //addLabelsOverlay(siteMapBounds); //addMapKeyOverlay(); getMarkerData('site-plan'); getMarkerData('commercial/commercial-map'); map.addListener("zoom_changed", function () { updateMarkerVisibility(); }); //// get user's location and add an icon to the map //addUserLocationToMap(map); } //init() function updateMarkerVisibility() { let currentZoom = map.getZoom(); markerArray.forEach(marker => { let shouldBeVisible = currentZoom >= marker.markerZoomLevel; // Show only when zoom reaches marker's level if (marker.getVisible() !== shouldBeVisible) { marker.setVisible(shouldBeVisible); } }); // Close all open info windows when zooming infoWindows.forEach(infoWindow => infoWindow.close()); } function getMarkerData(filename='site-plan') { fetch(`https://www.sayebrook.com/admin/sections/webpages/pages.cfc?method=getChildPagesJSON&filename=${filename}`) .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.json(); }) .then(data => { data.forEach(markerData => addCustomMarker(map, markerData)); }) .catch(error => console.error("Error loading marker data:", error)); } function addCustomMarker(map, markerData) { console.log('markerData:', markerData); if (!markerData.LATITUDE || !markerData.LONGITUDE) return; // Skip if missing coordinates let markerIcon = { url: markerIconURL, scaledSize: new google.maps.Size(26, 36), anchor: new google.maps.Point(13, 36), }; let marker = new google.maps.Marker({ position: { lat: parseFloat(markerData.LATITUDE), lng: parseFloat(markerData.LONGITUDE) }, map: map, icon: markerIcon, title: markerData.HEADLINE, visible: map.getZoom() >= markerData.MAP_ZOOM_LEVEL }); marker.markerZoomLevel = markerData.MAP_ZOOM_LEVEL; // Store marker-specific zoom level // Info Window for Marker let infoWindowContent = `

${markerData.HEADLINE}

`; let infoWindowContentPlain = `${markerData.HEADLINE}
View Details`; let infoWindow = new google.maps.InfoWindow({ //content: `${markerData.HEADLINE}
View Details`, content: infoWindowContent, pixelOffset: new google.maps.Size(0, 34) }); marker.addListener("click", function () { infoWindows.forEach(infoWindow => infoWindow.close()); infoWindow.open(map, marker); }); map.addListener("click", function () { infoWindow.close(); }); markerArray.push(marker); infoWindows.push(infoWindow); } //addCustomMarker function animateZoom(currentZoom, finalZoom) { let zoom = currentZoom; // Create an interval to increase the zoom level gradually const interval = setInterval(() => { if (zoom >= finalZoom) { clearInterval(interval); } else { zoom += .1; // Increment the zoom value (Adjust for smoother/slower zoom) map.setZoom(Math.round(zoom)); } }, 100); // Adjust interval time for the desired animation speed } function stepZoom() { if (zoomLevel >= targetZoom) { map.setZoom(targetZoom); } else { zoomLevel += 0.05; // Increment the zoom value for smoother zoom map.setZoom(Math.round(zoomLevel)); requestAnimationFrame(stepZoom); } } function loadGoogleMapsAPI() { const apiKey = 'AIzaSyDZFYCp5Jo1nClFTm6EAFnuGS31HMsFJvw'; // Replace with your actual Google Maps API key const script = document.createElement('script'); script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=initMap&loading=async`; script.async = true; script.defer = true; document.head.appendChild(script); } if (typeof google === 'undefined' || typeof google.maps === 'undefined') { loadGoogleMapsAPI(); } else { initMap(); } function handleMapEvents(map, overlay) { google.maps.event.addListener(map, 'zoom_changed', () => { const zoomLevel = map.getZoom(); infoWindows.forEach(infoWindow => infoWindow.close()); /*if (zoomLevel >= zLevelHide) { overlay.show(); } else { overlay.hide(); }*/ }); } ////////////////////// add user location /////////////////////////// function addUserLocationToMap(map) { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(function (position) { const userLocation = { lat: position.coords.latitude, lng: position.coords.longitude }; // Custom SVG as Data URL const userIcon = { url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(` `), scaledSize: new google.maps.Size(20, 32), // Resize if needed anchor: new google.maps.Point(10, 32) // Adjust anchor point if necessary }; // Add user marker new google.maps.Marker({ position: userLocation, map: map, title: "Your Location", icon: userIcon }); // Center the map on the user's location map.setCenter(userLocation); map.setZoom(15); // Adjust zoom level }, function (error) { console.error("Error getting user location:", error); }); } else { console.error("Geolocation is not supported by this browser."); } } async function getLotsAndFloorplans(phase = 0) { try { const url = new URL(`${baseUrl}/themes/${theme}/modules/siteplan/siteplan.cfc?method=getLotsAndFloorplans`); url.searchParams.append('phase', phase); const response = await fetch(url.toString(), { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const lot_data = await response.json(); return lot_data; } catch (error) { console.error('Failed to fetch lots and floorplans:', error); return null; } } /* getLotsAndFloorplans().then(data => { lotAndFloorplanData = JSON.parse(data); setLotStatus(); }).catch(error => { console.error('Error fetching data:', error); }); */ function setLotStatus() { console.log('lotAndFloorplanData',lotAndFloorplanData); lotAndFloorplanData.lots.forEach(lot => { $('#Lot' + lot.lotNumber).addClass(lot.lotSalesStatus); $('#Lot' + lot.lotNumber).attr('data-status', lot.lotSalesStatus); }); // Attach events after lot status is set /*if (sitePlanOverlay) { sitePlanOverlay.attachEvents(); }*/ } function handleLotClick(e, element) { e.stopPropagation(); if (!disableClicks) { createAndShowLotModal(element.id); } } function createAndShowLotModal(lotNumber) { const lotInfo = getLotByNumber(lotAndFloorplanData, lotNumber.replace('Lot', '')); if (lotInfo) { console.log('lotInfo',lotInfo); const lotSize = Number(lotInfo.lotSize).toLocaleString(); const salesStatus = ''; $('.modal').remove(); let modalContent = `
Home Site

${lotInfo.lotNumber}

${ formatStatus( lotInfo.lotSalesStatus )}
`; if (lotInfo.lotPriceOverride > 0) { modalContent += `
Lot/Home From

${ formatPrice( lotInfo.lotPriceOverride )}

` } modalContent += `

Floor Plans

Available for Home Site ${lotInfo.lotNumber}
${generateFloorPlanCards(lotInfo)}
`; const modalHtml = ` `; $('body').append(modalHtml); $('#lotModal').modal('show'); } } function generateFloorPlanCards(lotInfo) { let floorPlanRow = ``; lotInfo.lotFloorPlans.forEach(plan => { if (plan != null) { const heatedSqFt = Number(plan.floorHeatedSqFt).toLocaleString(); const totalSqFt = plan.floorTotalSqFt ? Number(plan.floorTotalSqFt).toLocaleString() : 0; let cardHtml = `
${plan.floorPlanName}
${plan.floorPlanName}
${heatedSqFt} Heated SqFt ${totalSqFt != '' ? `| ${totalSqFt} Total SqFt` : ``}
  • ${plan.floorPlanBeds} Beds
  • ${plan.floorPlanBaths} Baths
  • ${plan.floorPlanHalfBaths} Half Bath
  • ${plan.floorPlanStories} Stories
`; if (lotInfo.showLotHomePrice) { cardHtml += `
From ${ currency(plan.lotAndHomePrice, {precision: 0}).format() }
`; } cardHtml += `
`; floorPlanRow += cardHtml; } }); return floorPlanRow; } // Add event listener once for floor plan buttons $(document).on('click', '.btn-floor-plan', function (e) { const floorPlanId = $(this).data('floor-plan-id'); createAndShowFloorPlanModal(floorPlanId); }); function createAndShowFloorPlanModal(floorPlanId) { const getFloorPlanDetails = getFloorPlanById(lotAndFloorplanData, floorPlanId); if (getFloorPlanDetails) { $('.floorplan-modal').remove(); const heatedSqFt = Number(getFloorPlanDetails.floorHeatedSqFt).toLocaleString(); let floorPlanDetailsNoPriceHTML = `
`; const floorPlanDetailsHTML = `
`; $('body').append(floorPlanDetailsNoPriceHTML); $('#floorPlanModal').modal('show'); $('#floorPlanModal').on('click', '.close', function () { $('#floorPlanModal').modal('hide'); }); } } function getLotByNumber(data, lotNumber) { const lots = data.lots; for (let lot of lots) { if (lot.lotNumber === lotNumber) { return lot; } } return null; } function getFloorPlanById(data, floorPlanId) { for (let lot of data.lots) { for (let floorPlan of lot.lotFloorPlans) { if (floorPlan.floorPlanID === floorPlanId) { return floorPlan; } } } return null; } function addLabelsOverlay(siteMapBounds) { var srcImage = `/themes/${theme}/modules/siteplan/site-plan-labels.svg`; let labelOverlay = new google.maps.GroundOverlay(srcImage,siteMapBounds); console.log('labelOverlay',labelOverlay); labelOverlay.setMap(map); google.maps.event.addListenerOnce(map, 'idle', function(){ labelOverlay.setOptions({ zIndex: 10000 }); }); }//// end addLabelsOverlay function addMapKeyOverlay() { let srcImage = `/themes/${theme}/modules/siteplan/images/map-key.svg`; const mapKeyBounds = new google.maps.LatLngBounds( new google.maps.LatLng(31.038359, -81.413328), // Southwest corner new google.maps.LatLng(31.039054, -81.412435) // Northeast corner ); let mapKeyOverlay = new google.maps.GroundOverlay(srcImage,mapKeyBounds); mapKeyOverlay.setMap(map); google.maps.event.addListenerOnce(map, 'idle', function(){ mapKeyOverlay.setOptions({ zIndex: 1000 }); }); }//// end addMapKeyOverlay function displayMapInfo(info) { const div = document.getElementById('map-info'); console.log(div); if (div != null){ div.innerHTML += info + '
'; } } ///// helpers function formatPrice(num) { if (num >= 1000000) { // Number is at least 1 million let millions = num / 1000000; let rounded = millions.toFixed(1); return `$${rounded} M`; } else if (num >= 100000) { // Number is between 100,000 and 999,999 let thousands = Math.floor(num / 1000); let rounded = Math.floor(thousands / 10) * 10; return `$${rounded} K`; } else { // For numbers less than 100,000, output the exact number with a dollar sign return `$${num}`; } } function formatStatus(status) { return status.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); } ///// cookies function setCookie(name, value, days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); const expires = "expires=" + date.toUTCString(); document.cookie = name + "=" + value + ";" + expires + ";path=/"; } function getCookie(name) { const cname = name + "="; const decodedCookie = decodeURIComponent(document.cookie); const ca = decodedCookie.split(';'); for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') { c = c.substring(1); } if (c.indexOf(cname) === 0) { return c.substring(cname.length, c.length); } } return ""; } //////// tooltip for sales status //////// // Create tooltip div const tooltipDiv = document.createElement('div'); tooltipDiv.id = 'tooltip'; Object.assign(tooltipDiv.style, { position: 'absolute', pointerEvents: 'none', background: 'rgba(0, 0, 0, 0.7)', color: 'white', padding: '3px 6px', borderRadius: '4px', display: 'none', zIndex: '10000', fontSize: '10px' }); document.body.appendChild(tooltipDiv); function handleLotMouseOverORIG(e, element) { e.stopPropagation(); const status = element.getAttribute('data-status') || 'Call Us'; const formattedStatus = formatStatus(status); showTooltip(e, formattedStatus); } function handleLotMouseOver(e, element) { e.stopPropagation(); let status = element.getAttribute('data-status'); if (!status) { // If data-status is not yet set, default to a placeholder or fetch the status dynamically if possible status = 'Status Loading...'; } else { status = formatStatus(status); } const formattedStatus = formatStatus(status); showTooltip(e, formattedStatus); } function handleLotMouseOut(e, element) { e.stopPropagation(); hideTooltip(); } function showTooltip(e, status) { tooltipDiv.innerHTML = status; const mouseX = e.pageX; const mouseY = e.pageY; tooltipDiv.style.left = (mouseX + 10) + 'px'; tooltipDiv.style.top = (mouseY + 10) + 'px'; tooltipDiv.style.display = 'block'; } function hideTooltip() { tooltipDiv.style.display = 'none'; } // Optional: Update tooltip position on mouse move function updateTooltipPosition(e) { const mouseX = e.pageX; const mouseY = e.pageY; tooltipDiv.style.left = (mouseX + 10) + 'px'; tooltipDiv.style.top = (mouseY + 10) + 'px'; }