1let map; 2let markers = []; // To store all markers 3const allLocations = {}; // To store all locations with their group key 4 5// Define initMap in the global scope so Google Maps API can call it 6window.initMap = async function() { // Make initMap async 7 // Default map center (e.g., center of your area of interest) 8 const defaultCenter = { lat: 26.2445, lng: -80.2587 }; // Example: Coconut Creek, FL 9 10 const { Map } = await google.maps.importLibrary("maps"); 11 const { AdvancedMarkerElement } = await google.maps.importLibrary("marker"); 12 13 map = new Map(document.getElementById("map"), { 14 zoomControl: true, 15 center: defaultCenter, 16 mapId: "COCONUT_CREEK_DEMO_MAP" // Recommended for Advanced Markers 17 }); 18 19 // Now that the map is initialized, set up markers, listeners, etc. 20 await initializeMapFeatures(AdvancedMarkerElement); // Await the async function 21}; 22 23// This function contains the core logic for setting up map features 24async function initializeMapFeatures(AdvancedMarkerElement) { // Make initializeMapFeatures async 25 const groupSelectorsContainer = document.getElementById('map-group-selectors'); 26 const infoContentElement = document.getElementById('info-content'); 27 const mapElement = document.getElementById('map'); // Check for map element as well 28 let activeMarkerWrapper = null; // Variable to keep track of the active marker 29 30 // Define feature icon mapping 31 const featureIcons = { 32 'Kids Playground': 'rec.svg', 33 'Walk/Jog Path': 'walking.svg', 34 'Athletic Field': 'field.svg', 35 'Football Field': 'football.svg', 36 'Baseball': 'baseball.svg', 37 'Dog Walk Area': 'dog-walking.svg', 38 'Restrooms': 'man-woman.svg', 39 'Picnic Area': 'picnic.svg', 40 'Pavilion Rental': 'pavilion.svg', 41 'Hockey Rink': 'hockey.svg', 42 'Tennis Courts': 'tennis.svg', 43 'Nature Preserve': 'nature.svg', 44 'Lake/Canal View': 'canal.svg', 45 'Bird Watching': 'bird.svg' 46 }; 47 48 if (!groupSelectorsContainer || !infoContentElement || !mapElement) { 49 console.error('Map interactivity elements not found. Ensure #map-group-selectors, #info-content, and #map exist.'); 50 return; 51 } 52 53 // Define all locations first 54 const allLocationsList = [ 55 { 56 id: 'loc1', 57 image: '../img/locations/image1.jpg', 58 name: 'Tradewinds Park', 59 address: '3600 W Sample Rd, <br>Coconut Creek, FL 33073', 60 times: 'Mon - Thu: 7:00 AM - 10:00 PM, <br>Fri: 7:00 AM - 9:00 PM<br>Sat: 8:00 PM - 9:00 PM<br>Sun: 8:00 AM - 8:00 PM', 61 url: '/dummy-link', 62 features: { 63 'Kids Playground': true, 64 'Walk/Jog Path': true, 65 'Athletic Field': true, 66 'Football Field': true, 67 'Baseball': true, 68 'Dog Walk Area': true, 69 'Restrooms': true, 70 'Picnic Area': true, 71 'Pavilion Rental': true, 72 'Hockey Rink': true, 73 'Tennis Courts': true, 74 'Nature Preserve': true, 75 'Lake/Canal View': true, 76 'Bird Watching': true 77 }, 78 lat: 26.2598, lng: -80.1938, 79 categories: ['group2'] // City Parks 80 }, 81 { 82 id: 'loc2', 83 image: '../img/locations/image2.jpg', 84 name: 'Butterfly World', 85 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 86 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 87 features: { 88 89 }, 90 lat: 26.2600, 91 lng: -80.1900, 92 categories: ['group2'] // Greenways & Trails 93 }, 94 { 95 id: 'loc2', 96 image: '../img/locations/image2.jpg', 97 name: 'Butterfly World', 98 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 99 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 100 features: { 101 102 }, 103 lat: 26.2600, 104 lng: -80.1900, 105 categories: ['group2'] // Greenways & Trails 106 }, 107 { 108 id: 'loc2', 109 image: '../img/locations/image2.jpg', 110 name: 'Butterfly World', 111 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 112 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 113 features: { 114 115 }, 116 lat: 26.2600, 117 lng: -80.1900, 118 categories: ['group2'] // Greenways & Trails 119 }, 120 { 121 id: 'loc2', 122 image: '../img/locations/image2.jpg', 123 name: 'Butterfly World', 124 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 125 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 126 features: { 127 128 }, 129 lat: 26.2600, 130 lng: -80.1900, 131 categories: ['group2'] // Greenways & Trails 132 }, 133 { 134 id: 'loc2', 135 image: '../img/locations/image2.jpg', 136 name: 'Butterfly World', 137 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 138 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 139 features: { 140 141 }, 142 lat: 26.2600, 143 lng: -80.1900, 144 categories: ['group2'] // Greenways & Trails 145 }, 146 { 147 id: 'loc2', 148 image: '../img/locations/image2.jpg', 149 name: 'Butterfly World', 150 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 151 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 152 features: { 153 154 }, 155 lat: 26.2600, 156 lng: -80.1900, 157 categories: ['group2'] // Greenways & Trails 158 }, 159 { 160 id: 'loc2', 161 image: '../img/locations/image2.jpg', 162 name: 'Butterfly World', 163 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 164 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 165 features: { 166 167 }, 168 lat: 26.2600, 169 lng: -80.1900, 170 categories: ['group2'] // Greenways & Trails 171 }, 172 { 173 id: 'loc2', 174 image: '../img/locations/image2.jpg', 175 name: 'Butterfly World', 176 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 177 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 178 features: { 179 180 }, 181 lat: 26.2600, 182 lng: -80.1900, 183 categories: ['group2'] // Greenways & Trails 184 }, 185 { 186 id: 'loc2', 187 image: '../img/locations/image2.jpg', 188 name: 'Butterfly World', 189 address: '3600 W Sample Rd, Coconut Creek, FL 33073', 190 times: 'Mon - Sun: 9:00 AM - 5:00 PM', 191 features: { 192 193 }, 194 lat: 26.2600, 195 lng: -80.1900, 196 categories: ['group2'] // Greenways & Trails 197 }, 198 { 199 id: 'loc13', 200 image: '../img/locations/image3.jpg', 201 name: 'Coconut Creek Community Center', 202 address: '1010 Coconut Creek Blvd, Coconut Creek, FL 33066', 203 times: 'Mon - Fri: 8:00 AM - 10:00 PM, <br>Sat - Sun: 9:00 AM - 6:00 PM', 204 url: '/dummy-link', 205 features: { 206 'Kids Playground': true, 207 'Athletic Field': true, 208 'Restrooms': true, 209 'Picnic Area': true, 210 'Tennis Courts': true 211 }, 212 lat: 26.2520, 213 lng: -80.1850, 214 categories: ['group2'] // Recreation Centers 215 }, 216 { 217 id: 'loc14', 218 image: '../img/locations/image4.jpg', 219 name: 'Creek Side Park', 220 address: '2200 NW 40th Ave, Coconut Creek, FL 33066', 221 times: 'Daily: 6:00 AM - 10:00 PM', 222 url: '/dummy-link', 223 features: { 224 'Kids Playground': true, 225 'Walk/Jog Path': true, 226 'Dog Walk Area': true, 227 'Restrooms': true, 228 'Picnic Area': true,
229 'Nature Preserve': true, 230 'Lake/Canal View': true 231 }, 232 lat: 26.2650, 233 lng: -80.1920, 234 categories: ['group2'] // Athletic Fields 235 }, 236 { 237 id: 'loc3', 238 image: '../img/locations/image3.jpg', 239 name: 'Seminole Casino Coconut Creek', 240 address: '5550 NW 40th St, Coconut Creek, FL 33073', 241 times: 'Open 24 Hours lorem ipsum Lorem ipsum dolor sit amet, consectetur adipisicing elit. Totam excepturi, odit ratione quas itaque maiores placeat numquam nobis explicabo impedit iusto! Alias recusandae hic laudantium maxime excepturi ea commodi sunt. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Totam excepturi, odit ratione quas itaque maiores placeat numquam nobis explicabo impedit iusto! Alias recusandae hic laudantium maxime excepturi ea commodi sunt. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Totam excepturi, odit ratione quas itaque maiores placeat numquam nobis explicabo impedit iusto! Alias recusandae hic laudantium maxime excepturi ea commodi sunt. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Totam excepturi, odit ratione quas itaque maiores placeat numquam nobis explicabo impedit iusto! Alias recusandae hic laudantium maxime excepturi ea commodi sunt.', 242 features: { 243 'Kids Playground': true, 244 'Walk/Jog Path': true, 245 'Athletic Field': true, 246 'Baseball': true, 247 'Bird Watching': true 248 }, 249 lat: 26.2786, lng: -80.1987, 250 categories: ['group3'] // Greenways & Trails 251 }, 252 { 253 id: 'loc4', 254 image: '../img/locations/image5.jpg', 255 name: 'Promenade at Coconut Creek', 256 address: '4443 Lyons Rd, Coconut Creek, FL 33073', 257 times: 'Mon - Sat: 10:00 AM - 9:00 PM, <br>Sun: 12:00 PM - 6:00 PM', 258 features: { 259 'Kids Playground': true, 260 'Walk/Jog Path': true, 261 'Athletic Field': true, 262 'Baseball': true, 263 'Dog Walk Area': true, 264 'Restrooms': true, 265 'Picnic Area': true, 266 'Nature Preserve': true, 267 'Lake/Canal View': true, 268 'Bird Watching': true 269 }, 270 lat: 26.2850, 271 lng: -80.1760, 272 categories: ['group4'] // Playgrounds 273 }, 274 { 275 id: 'loc5', 276 image: '../img/locations/image6.jpg', 277 name: 'Windmill Park', 278 address: '1234 Windmill Dr, Coconut Creek, FL 33066', 279 times: 'Daily: 6:00 AM - 8:00 PM', 280 url: '/dummy-link', 281 features: { 282 'Kids Playground': true, 283 'Athletic Field': true, 284 'Restrooms': true, 285 'Picnic Area': true 286 }, 287 lat: 26.2580, 288 lng: -80.1950, 289 categories: ['group5'] // Recreation Centers 290 }, 291 { 292 id: 'loc6', 293 image: '../img/locations/image7.jpg', 294 name: 'Palm Beach Park', 295 address: '5678 Palm Beach Blvd, Coconut Creek, FL 33073', 296 times: 'Daily: 5:00 AM - 10:00 PM', 297 url: '/dummy-link', 298 features: { 299 'Walk/Jog Path': true, 300 'Athletic Field': true, 301 'Tennis Courts': true, 302 'Restrooms': true 303 }, 304 lat: 26.2720, 305 lng: -80.1800, 306 categories: ['group5'] // Athletic Fields 307 } 308 ]; 309 310 // Build locationsData by organizing locations into groups 311 const locationsData = {}; 312 313 // Initialize all groups 314 ['group2', 'group3', 'group4', 'group5', 'group6', 'group7', 'group8', 'group9', 'group10', 'group11', 'group12'].forEach(groupKey => { 315 locationsData[groupKey] = []; 316 }); 317 318 // Populate groups based on categories 319 allLocationsList.forEach(location => { 320 location.categories.forEach(category => { 321 if (locationsData[category]) { 322 // Add the location with the current category as its groupKey for this instance 323 locationsData[category].push({ 324 ...location, 325 groupKey: category 326 }); 327 } 328 }); 329 }); 330 331 // Fetch the SVG content for the map pin 332 let pinSvgString = ''; 333 try { 334 const response = await fetch('../img/location-pin.svg'); // Path relative to the HTML file 335 if (!response.ok) { 336 throw new Error(`HTTP error! status: ${response.status}`); 337 } 338 pinSvgString = await response.text(); 339 } catch (error) { 340 console.error('Failed to fetch map pin SVG:', error); 341 // Provide a simple fallback SVG string in case of fetch failure 342 pinSvgString = '<svg viewBox="0 0 24 24" width="28" height="41" fill="currentColor" style="display:block;"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"></path></svg>'; 343 } 344 345 // Populate allLocations and create markers 346 // Create markers only once per unique location ID to avoid duplicates 347 const createdMarkers = new Set(); 348 349 for (const groupKey in locationsData) {
350 locationsData[groupKey].forEach(location => { 351 // Store location in allLocations if not already stored 352 if (!allLocations[location.id]) { 353 allLocations[location.id] = { ...location }; 354 } 355 356 // Create marker only if we haven't created one for this location ID yet 357 if (!createdMarkers.has(location.id)) { 358 // Create a div element to hold the SVG pin 359 const pinElement = document.createElement('div'); 360 pinElement.innerHTML = pinSvgString; 361 pinElement.style.width = '28px'; 362 pinElement.style.height = '41px'; 363 pinElement.style.cursor = 'pointer'; 364 365 const svgElement = pinElement.querySelector('svg'); 366 if (svgElement) { 367 svgElement.style.width = '100%'; 368 svgElement.style.height = '100%'; 369 svgElement.style.display = 'block'; 370 } 371 372 const markerElement = new AdvancedMarkerElement({ 373 map: null, // Start with markers hidden 374 position: { lat: location.lat, lng: location.lng }, 375 title: location.name, 376 content: pinElement, // Use the div containing the SVG 377 }); 378 379 // Store marker and its associated data 380 const markerWrapper = { 381 element: markerElement, 382 customInfo: location, 383 visible: false // Our custom visibility flag 384 }; 385 386 // Add click listener directly to the marker element 387 markerElement.addListener('click', () => { 388 if (activeMarkerWrapper && activeMarkerWrapper.element.content) { 389 activeMarkerWrapper.element.content.classList.remove('active-pin'); 390 } 391 if (markerWrapper.element.content) { 392 markerWrapper.element.content.classList.add('active-pin'); 393 } 394 activeMarkerWrapper = markerWrapper; 395 396 // Hide all markers first 397 markers.forEach(marker => { 398 marker.element.map = null; 399 marker.visible = false; 400 }); 401 402 // Show only this specific marker 403 markerWrapper.element.map = map; 404 markerWrapper.visible = true; 405 406 // Center map on the marker 407 map.setCenter(markerWrapper.element.position); 408 map.setZoom(16); 409 410 // Find the best group to highlight for this location 411 const bestGroupKey = findBestGroupForLocation(location.id); 412 selectGroupButton(bestGroupKey); 413 414 // Show only this individual location's details 415 updateInfoPanel(location); 416 }); 417 418 markers.push(markerWrapper); 419 createdMarkers.add(location.id); 420 } 421 }); 422 } 423 424 // Helper function to add hover interactions between pins and cards 425 function addHoverInteractions() { 426 markers.forEach(markerWrapper => { 427 const locationId = markerWrapper.customInfo.id; 428 const pinElement = markerWrapper.element.content; 429 430 if (pinElement) { 431 // Remove existing listeners to avoid duplicates 432 pinElement.onmouseenter = null; 433 pinElement.onmouseleave = null; 434 435 // Add hover listeners directly to the pin element 436 pinElement.addEventListener('mouseenter', () => { 437 pinElement.classList.add('hover-pin'); 438 // Find and highlight corresponding location card 439 const locationCard = document.querySelector(`[data-location-id="${locationId}"]`); 440 if (locationCard) { 441 locationCard.classList.add('hover-state'); 442 } 443 }); 444 445 pinElement.addEventListener('mouseleave', () => { 446 pinElement.classList.remove('hover-pin'); 447 // Remove highlight from corresponding location card 448 const locationCard = document.querySelector(`[data-location-id="${locationId}"]`); 449 if (locationCard) { 450 locationCard.classList.remove('hover-state'); 451 } 452 }); 453 } 454 }); 455 } 456 457 function updateInfoPanel(location) { 458 if (location) { 459 let imageHtml = ''; 460 if (location.image) { 461 imageHtml = `<figure class="w-full mx-auto overflow-hidden mb-4"><img src="${location.image}" alt="${location.name}" width="520" height="350" class="object-cover w-full h-auto"></figure>`; 462 } 463 464 let featuresHtml = ''; 465 if (location.features && typeof location.features === 'object') { 466 featuresHtml = Object.keys(location.features) 467 .filter(feature => location.features[feature] === true) 468 .map(feature => { 469 const iconFilename = featureIcons[feature]; 470 const iconPath = iconFilename ? `../svg/Icons/features/${iconFilename}` : ''; 471 const iconTag = iconPath ? `<span class="w-14 h-14 roun
471ded-full grid items-center justify-center border-2 border-black xl:w-18 xl:h-18"><img src="${iconPath}" alt="${feature} icon" class="feature-list--icon"></span>` : '<span class="mr-2">[?]</span>'; 472 return ` 473 <li class="grid justify-center justify-items-center my-1 gap-1 text-center w-[calc(33.333%-1rem)] xl:w-[calc(25%-1rem)]"> 474 ${iconTag} 475 ${feature} 476 </li> 477 `; 478 }).join(''); 479 } 480 481 infoContentElement.innerHTML = ` 482 <div> 483 ${imageHtml} 484 <div class="py-6 px-5"> 485 <h3 class="mb-5 lg:mb-10">${location.name}</h3> 486 <p class="text-sm mt-1">${location.address}</p> 487 ${location.times ? `<p class="text-sm mt-1">${location.times}</p>` : ''} 488 ${featuresHtml ? ` 489 <div> 490 <p class="font-bold mt-4">Features and Amenities</p> 491 <ul class="feature-list list-none flex flex-wrap justify-start gap-4"> 492 ${featuresHtml} 493 </ul> 494 </div> 495 ` : ''} 496 ${location.url ? ` 497 <a href="${location.url}" class="btn mt-4"> 498 Learn more about ${location.name} 499 <i class="icon-chevron"></i> 500 </a> 501 ` : ''} 502 </div> 503 </div> 504 `; 505 } else { 506 infoContentElement.innerHTML = '<p class="text-gray-700">Select a group or click a pin on the map to see details here.</p>'; 507 } 508 } 509 510 function updateInfoPanelWithAllLocations(selectedGroupKey) { 511 if (!infoContentElement) return; 512 513 const groupLocations = locationsData[selectedGroupKey] || []; 514 515 if (groupLocations.length === 0) { 516 infoContentElement.innerHTML = '<p class="text-gray-700">No locations found for this group.</p>'; 517 return; 518 } 519 520 const locationCards = groupLocations.map((location, index) => { 521 let imageHtml = ''; 522 if (location.image) { 523 imageHtml = `<figure class="w-full mx-auto overflow-hidden"><img src="${location.image}" alt="${location.name}" width="520" height="350" class="object-cover w-full h-auto"></figure>`; 524 } 525 526 // Add active class to the first location card 527 const activeClass = index === 0 ? ' active-location-card' : ''; 528 529 return ` 530 <div class="flex location-card border-b-[1px] border-b-black cursor-pointer px-3.5 py-5 gap-4 hover:bg-orange-200 hover:text-white transition-all duration-300${activeClass}" data-location-id="${location.id}"> 531 <div class="w-[70%]"> 532 <h3 class="text-lg font-semibold mb-2">${location.name}</h3> 533 <p class="text-sm mb-0">${location.address}</p> 534 </div> 535 <div class="w-[30%]"> 536 ${imageHtml} 537 </div> 538 </div> 539 `; 540 }).join(''); 541 542 infoContentElement.innerHTML = ` 543 544 ${locationCards} 545 546 `; 547 548 // Add click handlers to location cards 549 const locationCardElements = infoContentElement.querySelectorAll('.location-card'); 550 locationCardElements.forEach(card => { 551 card.addEventListener('click', (e) => { 552 e.preventDefault(); 553 const locationId = card.dataset.locationId; 554 const location = allLocations[locationId]; 555 556 if (location) { 557 // Remove active state from all location cards 558 locationCardElements.forEach(c => c.classList.remove('active-location-card')); 559 // Add active state to clicked card 560 card.classList.add('active-location-card'); 561 562 // Find and activate the corresponding marker 563 const markerWrapper = markers.find(m => m.customInfo.id === locationId); 564 if (markerWrapper) { 565 // Clear previous active marker 566 if (activeMarkerWrapper && activeMarkerWrapper.element.content) { 567 activeMarkerWrapper.element.content.classList.remove('active-pin'); 568 } 569 570 // Set new active marker 571 if (markerWrapper.element.content) { 572 markerWrapper.element.content.classList.add('active-pin'); 573 } 574 activeMarkerWrapper = markerWrapper; 575 576 // Hide all markers first
577 markers.forEach(marker => { 578 marker.element.map = null; 579 marker.visible = false; 580 }); 581 582 // Show only this specific marker 583 markerWrapper.element.map = map; 584 markerWrapper.visible = true; 585 586 // Center map on the marker 587 map.setCenter(markerWrapper.element.position); 588 map.setZoom(16); 589 590 // Find the best group to highlight for this location 591 const bestGroupKey = findBestGroupForLocation(locationId); 592 selectGroupButton(bestGroupKey); 593 594 // Show only this individual location's details 595 updateInfoPanel(location); 596 } 597 } 598 }); 599 600 // Add hover listeners for location cards 601 card.addEventListener('mouseenter', () => { 602 const locationId = card.dataset.locationId; 603 card.classList.add('hover-state'); 604 605 // Find and highlight corresponding marker 606 const markerWrapper = markers.find(m => m.customInfo.id === locationId); 607 if (markerWrapper && markerWrapper.element.content) { 608 markerWrapper.element.content.classList.add('hover-pin'); 609 } 610 }); 611 612 card.addEventListener('mouseleave', () => { 613 const locationId = card.dataset.locationId; 614 card.classList.remove('hover-state'); 615 616 // Remove highlight from corresponding marker 617 const markerWrapper = markers.find(m => m.customInfo.id === locationId); 618 if (markerWrapper && markerWrapper.element.content) { 619 markerWrapper.element.content.classList.remove('hover-pin'); 620 } 621 }); 622 }); 623 624 // Add hover interactions between pins and cards 625 addHoverInteractions(); 626 } 627 628 function selectGroupButton(groupKey) { 629 const buttons = groupSelectorsContainer.querySelectorAll('[data-group-button]'); 630 buttons.forEach(button => { 631 if (button.dataset.groupKey === groupKey) { 632 button.classList.add('active'); 633 } else { 634 button.classList.remove('active'); 635 } 636 }); 637 } 638 639 function handleGroupSelected(selectedGroupKey) { 640 const bounds = new google.maps.LatLngBounds(); 641 let firstLocationInGroup = null; 642 let hasVisibleMarkersInGroup = false; 643 let newActiveMarkerForGroup = null; 644 645 // Clear previous active marker's style 646 if (activeMarkerWrapper && activeMarkerWrapper.element.content) { 647 activeMarkerWrapper.element.content.classList.remove('active-pin'); 648 } 649 650 // Get locations that belong to the selected group 651 const selectedGroupLocations = locationsData[selectedGroupKey] || []; 652 const selectedLocationIds = selectedGroupLocations.map(loc => loc.id); 653 654 markers.forEach(markerWrapper => { 655 // Show marker if its location belongs to the selected group 656 if (selectedLocationIds.includes(markerWrapper.customInfo.id)) { 657 markerWrapper.element.map = map; // Show marker by assigning to map 658 markerWrapper.visible = true; 659 bounds.extend(markerWrapper.element.position); // Use position from AdvancedMarkerElement 660 if (!firstLocationInGroup) { 661 firstLocationInGroup = markerWrapper.customInfo; 662 newActiveMarkerForGroup = markerWrapper; // This will be the new active marker for the group 663 } 664 hasVisibleMarkersInGroup = true; 665 } else { 666 markerWrapper.element.map = null; // Hide marker by removing from map 667 markerWrapper.visible = false; 668 } 669 }); 670 671 if (hasVisibleMarkersInGroup && newActiveMarkerForGroup) { 672 map.fitBounds(bounds); 673 // Prevent zooming too far if only one marker
674 if (bounds.getNorthEast().equals(bounds.getSouthWest())) { 675 map.setZoom(15); // Preferred zoom level for a single marker 676 } else { 677 // Adjust zoom after fitBounds to zoom in a bit more 678 setTimeout(() => { 679 const currentZoom = map.getZoom(); 680 map.setZoom(currentZoom + 0); // Zoom in one level more than fitBounds 681 }, 100); 682 } 683 684 // Show all locations in the group 685 updateInfoPanelWithAllLocations(selectedGroupKey); 686 687 // Set the new active marker and apply its style for the first location 688 if (newActiveMarkerForGroup.element.content) { 689 newActiveMarkerForGroup.element.content.classList.add('active-pin'); 690 } 691 activeMarkerWrapper = newActiveMarkerForGroup; 692 693 // After the info panel is updated, ensure the first location card also has active state 694 setTimeout(() => { 695 const firstLocationCard = infoContentElement.querySelector('.location-card'); 696 if (firstLocationCard) { 697 firstLocationCard.classList.add('active-location-card'); 698 } 699 // Re-add hover interactions after cards are created 700 addHoverInteractions(); 701 }, 50); 702 703 // console.log(`Group ${selectedGroupKey} selected. Displaying first location:`, firstLocationInGroup); 704 } else { 705 updateInfoPanelWithAllLocations(selectedGroupKey); // Show empty state for group 706 activeMarkerWrapper = null; // No active marker if group is empty or no visible markers 707 // console.log(`Group ${selectedGroupKey} selected, but no locations found or all are hidden.`); 708 } 709 710 // Highlight the active button 711 selectGroupButton(selectedGroupKey); 712 } 713 714 function findBestGroupForLocation(locationId) { 715 // Find the location in our original data 716 const location = allLocationsList.find(loc => loc.id === locationId); 717 if (!location || !location.categories || location.categories.length === 0) { 718 return 'group2'; // fallback to group2 719 } 720 721 // Return the first category for this location 722 return location.categories[0]; 723 } 724 725 if (groupSelectorsContainer) { 726 const allGroupButtons = groupSelectorsContainer.querySelectorAll('[data-group-button]'); 727 const toggleMobileButton = document.getElementById('group-toggle-mobile'); 728 const additionalButtonsWrapper = document.getElementById('additional-group-buttons'); 729 const toggleArrowDown = toggleMobileButton ? toggleMobileButton.querySelector('.icon-chevron') : null; 730 731 let firstGroupKey = null; 732 733 if (!allGroupButtons.length) { 734 console.error('No group selector buttons found with data-group-button attribute.'); 735 } 736 737 allGroupButtons.forEach((button, index) => { 738 const groupKey = button.dataset.groupKey; // Read directly from data-group-key attribute 739 740 if (groupKey) { 741 button.dataset.group = groupKey; // Set data-group for consistency with handleGroupSelected 742 if (index === 0) { 743 firstGroupKey = groupKey; 744 } 745 746 button.addEventListener('click', () => { 747 handleGroupSelected(button.dataset.group); 748 // If on mobile and additional buttons are open, and a button within it is clicked, consider closing it. 749 if (window.innerWidth < 768 && additionalButtonsWrapper && additionalButtonsWrapper.style.maxHeight !== '0px') { 750 // Example: toggleAdditionalButtons(false); // if you want to close it after selection 751 } 752 }); 753 } else { 754 // Log a warning if a button that is supposed to be a group selector is missing the key 755 console.warn('Button found without a data-group-key attribute, it will not function for group selection:', button); 756 } 757 }); 758 759 function toggleAdditionalButtons(forceOpen) { 760 const isCurrentlyOpen = additionalButtonsWrapper.style.maxHeight !== '0px'; 761 const open = forceOpen !== undefined ? forceOpen : !isCurrentlyOpen; 762 763 if (open) { 764 additionalButtonsWrapper.style.maxHeight = additionalButtonsWrapper.scrollHeight + "px"; 765 additionalButtonsWrapper.classList.add('is-open'); // Add class when open 766 if (toggleArrowDown) toggleArrowDown.classList.add('active'); 767 } else { 768 additionalButtonsWrapper.style.maxHeight = '0px'; 769 additionalButtonsWrapper.classList.remove('is-open'); // Remove class when closed 770 if (toggleArrowDown) toggleArrowDown.classList.remove('active'); 771 } 772 } 773 774 if (toggleMobileButton && additionalButtonsWrapper) { 775 // Ensure correct initial state on mobile (collapsed) 776 if (window.innerWidth < 768) { 777 additionalButtonsWrapper.style.maxHeight = '0px'; 778 } else { 779 // On desktop, ensure it's expanded (or rather, not constrained by max-height for md:contents to work) 780 additionalButtonsWrapper.style.maxHeight = 'none'; 781 } 782 783 toggleMobileButton.addEventListener('click', () => toggleAdditionalButtons()); 784 785 // Adjust on resize 786 window.addEventListener('resize', () => { 787 if (window.innerWidth >= 768) { 788 additionalButtonsWrapper.style.maxHeight = 'none'; 789 if (toggleArrowDown) toggleArrowDown.classList.remove('active'); 790 } else { 791 if (additionalButtonsWrapper.style.maxHeight === 'none') { 792 additionalButtonsWrapper.style.maxHeight = '0px'; 793 if (toggleArrowDown) toggleArrowDown.classList.remove('active'); 794 } 795 } 796 }); 797 } 798 799 // Initialize with the first group selected 800 if (firstGroupKey && locationsData[firstGroupKey]) { 801 handleGroupSelected(firstGroupKey); 802 } else if (Object.keys(locationsData).length > 0) { 803 const firstDataGroupKey = Object.keys(locationsData)[0]; 804 if (locationsData[firstDataGroupKey]) { 805 handleGroupSelected(firstDataGroupKey); 806 } 807 } else { 808 updateInfoPanel(null); 809 } 810 } else { 811 console.error('Group selectors container not found.'); 812 } 813}
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.