您可以透過進階標記,使用自訂 HTML 和 CSS 建立具有互動及動畫效果的顯眼標記。所有 AdvancedMarkerElement
執行個體都會以 HTML 元素的形式加進 DOM,這類元素可透過 element
屬性存取,且操控方式與其他 DOM 元素相同。AdvancedMarkerElement
是 DOM 元素,因此您可以直接將 CSS 樣式套用至預設標記,並使用 HTML 和 CSS 完全從頭開始建立自訂標記。
簡易 HTML 標記
這張地圖範例說明如何建立簡易的自訂 HTML 標記:
查看原始碼
下方範例說明如何建立新的 DIV 元素,指派 CSS 類別和文字內容給 DIV,再將 DIV 當做 AdvancedMarkerElement.content
的值傳遞:
TypeScript
async function initMap() { // Request needed libraries. const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary; const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary; const map = new Map(document.getElementById('map') as HTMLElement, { center: { lat: 37.42, lng: -122.1 }, zoom: 14, mapId: '4504f8b37365c3d0', }); const priceTag = document.createElement('div'); priceTag.className = 'price-tag'; priceTag.textContent = '$2.5M'; const marker = new AdvancedMarkerElement({ map, position: { lat: 37.42, lng: -122.1 }, content: priceTag, }); } initMap();
JavaScript
async function initMap() { // Request needed libraries. const { Map } = await google.maps.importLibrary("maps"); const { AdvancedMarkerElement } = await google.maps.importLibrary("marker"); const map = new Map(document.getElementById("map"), { center: { lat: 37.42, lng: -122.1 }, zoom: 14, mapId: "4504f8b37365c3d0", }); const priceTag = document.createElement("div"); priceTag.className = "price-tag"; priceTag.textContent = "$2.5M"; const marker = new AdvancedMarkerElement({ map, position: { lat: 37.42, lng: -122.1 }, content: priceTag, }); } initMap();
CSS
/* * Always set the map height explicitly to define the size of the div element * that contains the map. */ #map { height: 100%; } /* * Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } /* HTML marker styles */ .price-tag { background-color: #4285F4; border-radius: 8px; color: #FFFFFF; font-size: 14px; padding: 10px 15px; position: relative; } .price-tag::after { content: ""; position: absolute; left: 50%; top: 100%; transform: translate(-50%, 0); width: 0; height: 0; border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 8px solid #4285F4; }
HTML
<html> <head> <title>Advanced Marker Simple HTML</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div id="map"></div> <!-- prettier-ignore --> <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) ({key: "AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg", v: "weekly"});</script> </body> </html>
測試範例
互動式標記
本例說明如何建立一組互動式標記,在使用者按下標記時顯示虛構資訊。例子中的大部分功能都包含在 CSS 內。
查看原始碼
TypeScript
async function initMap() { // Request needed libraries. const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary; const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary; const center = {lat: 37.43238031167444, lng: -122.16795397128632}; const map = new Map(document.getElementById("map") as HTMLElement, { zoom: 11, center, mapId: "4504f8b37365c3d0", }); for (const property of properties) { const AdvancedMarkerElement = new google.maps.marker.AdvancedMarkerElement({ map, content: buildContent(property), position: property.position, title: property.description, }); AdvancedMarkerElement.addListener("click", () => { toggleHighlight(AdvancedMarkerElement, property); }); } } function toggleHighlight(markerView, property) { if (markerView.content.classList.contains("highlight")) { markerView.content.classList.remove("highlight"); markerView.zIndex = null; } else { markerView.content.classList.add("highlight"); markerView.zIndex = 1; } } function buildContent(property) { const content = document.createElement("div"); content.classList.add("property"); content.innerHTML = ` <div class="icon"> <i aria-hidden="true" class="fa fa-icon fa-${property.type}" title="${property.type}"></i> <span class="fa-sr-only">${property.type}</span> </div> <div class="details"> <div class="price">${property.price}</div> <div class="address">${property.address}</div> <div class="features"> <div> <i aria-hidden="true" class="fa fa-bed fa-lg bed" title="bedroom"></i> <span class="fa-sr-only">bedroom</span> <span>${property.bed}</span> </div> <div> <i aria-hidden="true" class="fa fa-bath fa-lg bath" title="bathroom"></i> <span class="fa-sr-only">bathroom</span> <span>${property.bath}</span> </div> <div> <i aria-hidden="true" class="fa fa-ruler fa-lg size" title="size"></i> <span class="fa-sr-only">size</span> <span>${property.size} ft<sup>2</sup></span> </div> </div> </div> `; return content; } const properties = [{ address: '215 Emily St, MountainView, CA', description: 'Single family house with modern design', price: '$ 3,889,000', type: 'home', bed: 5, bath: 4.5, size: 300, position: { lat: 37.50024109655184, lng: -122.28528451834352, }, }, { address: '108 Squirrel Ln 🐿, Menlo Park, CA', description: 'Townhouse with friendly neighbors', price: '$ 3,050,000', type: 'building', bed: 4, bath: 3, size: 200, position: { lat: 37.44440882321596, lng: -122.2160620727, }, }, { address: '100 Chris St, Portola Valley, CA', description: 'Spacious warehouse great for small business', price: '$ 3,125,000', type: 'warehouse', bed: 4, bath: 4, size: 800, position: { lat: 37.39561833718522, lng: -122.21855116258479, }, }, { address: '98 Aleh Ave, Palo Alto, CA', description: 'A lovely store on busy road', price: '$ 4,225,000', type: 'store-alt', bed: 2, bath: 1, size: 210, position: { lat: 37.423928529779644, lng: -122.1087629822001, }, }, { address: '2117 Su St, MountainView, CA', description: 'Single family house near golf club', price: '$ 1,700,000', type: 'home', bed: 4, bath: 3, size: 200, position: { lat: 37.40578635332598, lng: -122.15043378466069, }, }, { address: '197 Alicia Dr, Santa Clara, CA', description: 'Multifloor large warehouse', price: '$ 5,000,000', type: 'warehouse', bed: 5, bath: 4, size: 700, position: { lat: 37.36399747905774, lng: -122.10465384268522, }, }, { address: '700 Jose Ave, Sunnyvale, CA', description: '3 storey townhouse with 2 car garage', price: '$ 3,850,000', type: 'building', bed: 4, bath: 4, size: 600, position: { lat: 37.38343706184458, lng: -122.02340436985183, }, }, { address: '868 Will Ct, Cupertino, CA', description: 'Single family house in great school zone', price: '$ 2,500,000', type: 'home', bed: 3, bath: 2, size: 100, position: { lat: 37.34576403052, lng: -122.04455090047453, }, }, { address: '655 Haylee St, Santa Clara, CA', description: '2 storey store with large storage room', price: '$ 2,500,000', type: 'store-alt', bed: 3, bath: 2, size: 450, position: { lat: 37.362863347890716, lng: -121.97802139023555, }, }, { address: '2019 Natasha Dr, San Jose, CA', description: 'Single family house', price: '$ 2,325,000', type: 'home', bed: 4, bath: 3.5, size: 500, position: { lat: 37.41391636421949, lng: -121.94592071575907, }, }]; initMap();
JavaScript
async function initMap() { // Request needed libraries. const { Map } = await google.maps.importLibrary("maps"); const { AdvancedMarkerElement } = await google.maps.importLibrary("marker"); const center = { lat: 37.43238031167444, lng: -122.16795397128632 }; const map = new Map(document.getElementById("map"), { zoom: 11, center, mapId: "4504f8b37365c3d0", }); for (const property of properties) { const AdvancedMarkerElement = new google.maps.marker.AdvancedMarkerElement({ map, content: buildContent(property), position: property.position, title: property.description, }); AdvancedMarkerElement.addListener("click", () => { toggleHighlight(AdvancedMarkerElement, property); }); } } function toggleHighlight(markerView, property) { if (markerView.content.classList.contains("highlight")) { markerView.content.classList.remove("highlight"); markerView.zIndex = null; } else { markerView.content.classList.add("highlight"); markerView.zIndex = 1; } } function buildContent(property) { const content = document.createElement("div"); content.classList.add("property"); content.innerHTML = ` <div class="icon"> <i aria-hidden="true" class="fa fa-icon fa-${property.type}" title="${property.type}"></i> <span class="fa-sr-only">${property.type}</span> </div> <div class="details"> <div class="price">${property.price}</div> <div class="address">${property.address}</div> <div class="features"> <div> <i aria-hidden="true" class="fa fa-bed fa-lg bed" title="bedroom"></i> <span class="fa-sr-only">bedroom</span> <span>${property.bed}</span> </div> <div> <i aria-hidden="true" class="fa fa-bath fa-lg bath" title="bathroom"></i> <span class="fa-sr-only">bathroom</span> <span>${property.bath}</span> </div> <div> <i aria-hidden="true" class="fa fa-ruler fa-lg size" title="size"></i> <span class="fa-sr-only">size</span> <span>${property.size} ft<sup>2</sup></span> </div> </div> </div> `; return content; } const properties = [ { address: "215 Emily St, MountainView, CA", description: "Single family house with modern design", price: "$ 3,889,000", type: "home", bed: 5, bath: 4.5, size: 300, position: { lat: 37.50024109655184, lng: -122.28528451834352, }, }, { address: "108 Squirrel Ln 🐿, Menlo Park, CA", description: "Townhouse with friendly neighbors", price: "$ 3,050,000", type: "building", bed: 4, bath: 3, size: 200, position: { lat: 37.44440882321596, lng: -122.2160620727, }, }, { address: "100 Chris St, Portola Valley, CA", description: "Spacious warehouse great for small business", price: "$ 3,125,000", type: "warehouse", bed: 4, bath: 4, size: 800, position: { lat: 37.39561833718522, lng: -122.21855116258479, }, }, { address: "98 Aleh Ave, Palo Alto, CA", description: "A lovely store on busy road", price: "$ 4,225,000", type: "store-alt", bed: 2, bath: 1, size: 210, position: { lat: 37.423928529779644, lng: -122.1087629822001, }, }, { address: "2117 Su St, MountainView, CA", description: "Single family house near golf club", price: "$ 1,700,000", type: "home", bed: 4, bath: 3, size: 200, position: { lat: 37.40578635332598, lng: -122.15043378466069, }, }, { address: "197 Alicia Dr, Santa Clara, CA", description: "Multifloor large warehouse", price: "$ 5,000,000", type: "warehouse", bed: 5, bath: 4, size: 700, position: { lat: 37.36399747905774, lng: -122.10465384268522, }, }, { address: "700 Jose Ave, Sunnyvale, CA", description: "3 storey townhouse with 2 car garage", price: "$ 3,850,000", type: "building", bed: 4, bath: 4, size: 600, position: { lat: 37.38343706184458, lng: -122.02340436985183, }, }, { address: "868 Will Ct, Cupertino, CA", description: "Single family house in great school zone", price: "$ 2,500,000", type: "home", bed: 3, bath: 2, size: 100, position: { lat: 37.34576403052, lng: -122.04455090047453, }, }, { address: "655 Haylee St, Santa Clara, CA", description: "2 storey store with large storage room", price: "$ 2,500,000", type: "store-alt", bed: 3, bath: 2, size: 450, position: { lat: 37.362863347890716, lng: -121.97802139023555, }, }, { address: "2019 Natasha Dr, San Jose, CA", description: "Single family house", price: "$ 2,325,000", type: "home", bed: 4, bath: 3.5, size: 500, position: { lat: 37.41391636421949, lng: -121.94592071575907, }, }, ]; initMap();
CSS
:root { --building-color: #FF9800; --house-color: #0288D1; --shop-color: #7B1FA2; --warehouse-color: #558B2F; } /* * Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } /* * Always set the map height explicitly to define the size of the div element * that contains the map. */ #map { height: 100%; width: 100%; } /* * Property styles in unhighlighted state. */ .property { align-items: center; background-color: #FFFFFF; border-radius: 50%; color: #263238; display: flex; font-size: 14px; gap: 15px; height: 30px; justify-content: center; padding: 4px; position: relative; position: relative; transition: all 0.3s ease-out; width: 30px; } .property::after { border-left: 9px solid transparent; border-right: 9px solid transparent; border-top: 9px solid #FFFFFF; content: ""; height: 0; left: 50%; position: absolute; top: 95%; transform: translate(-50%, 0); transition: all 0.3s ease-out; width: 0; z-index: 1; } .property .icon { align-items: center; display: flex; justify-content: center; color: #FFFFFF; } .property .icon svg { height: 20px; width: auto; } .property .details { display: none; flex-direction: column; flex: 1; } .property .address { color: #9E9E9E; font-size: 10px; margin-bottom: 10px; margin-top: 5px; } .property .features { align-items: flex-end; display: flex; flex-direction: row; gap: 10px; } .property .features > div { align-items: center; background: #F5F5F5; border-radius: 5px; border: 1px solid #ccc; display: flex; font-size: 10px; gap: 5px; padding: 5px; } /* * Property styles in highlighted state. */ .property.highlight { background-color: #FFFFFF; border-radius: 8px; box-shadow: 10px 10px 5px rgba(0, 0, 0, 0.2); height: 80px; padding: 8px 15px; width: auto; } .property.highlight::after { border-top: 9px solid #FFFFFF; } .property.highlight .details { display: flex; } .property.highlight .icon svg { width: 50px; height: 50px; } .property .bed { color: #FFA000; } .property .bath { color: #03A9F4; } .property .size { color: #388E3C; } /* * House icon colors. */ .property.highlight:has(.fa-house) .icon { color: var(--house-color); } .property:not(.highlight):has(.fa-house) { background-color: var(--house-color); } .property:not(.highlight):has(.fa-house)::after { border-top: 9px solid var(--house-color); } /* * Building icon colors. */ .property.highlight:has(.fa-building) .icon { color: var(--building-color); } .property:not(.highlight):has(.fa-building) { background-color: var(--building-color); } .property:not(.highlight):has(.fa-building)::after { border-top: 9px solid var(--building-color); } /* * Warehouse icon colors. */ .property.highlight:has(.fa-warehouse) .icon { color: var(--warehouse-color); } .property:not(.highlight):has(.fa-warehouse) { background-color: var(--warehouse-color); } .property:not(.highlight):has(.fa-warehouse)::after { border-top: 9px solid var(--warehouse-color); } /* * Shop icon colors. */ .property.highlight:has(.fa-shop) .icon { color: var(--shop-color); } .property:not(.highlight):has(.fa-shop) { background-color: var(--shop-color); } .property:not(.highlight):has(.fa-shop)::after { border-top: 9px solid var(--shop-color); }
HTML
<html> <head> <title>Advanced Markers with HTML</title> <script src="https://use.fontawesome.com/releases/v6.2.0/js/all.js"></script> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div id="map"></div> <!-- prettier-ignore --> <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) ({key: "AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg", v: "weekly"});</script> </body> </html>
測試範例
動畫標記
本範例使用 CSS 和進階標記來製作傳統的「彈起落下」動畫。在 IntersectionObserver
中,會新增 drop
CSS 樣式。IntersectionObserver
會觀察每個標記進入可視區域的時機,並加入樣式。接著,createMarker()
函式在每個標記中加入的 animationend
事件監聽器會移除該樣式。
查看原始碼
TypeScript
/** * Returns a random lat lng position within the map bounds. * @param {!google.maps.Map} map * @return {!google.maps.LatLngLiteral} */ function getRandomPosition(map) { const bounds = map.getBounds(); const minLat = bounds.getSouthWest().lat(); const minLng = bounds.getSouthWest().lng(); const maxLat = bounds.getNorthEast().lat(); const maxLng = bounds.getNorthEast().lng(); const latRange = maxLat - minLat; // Note: longitude can span from a positive longitude in the west to a // negative one in the east. e.g. 150lng (150E) <-> -30lng (30W) is a large // span that covers the whole USA. let lngRange = maxLng - minLng; if (maxLng < minLng) { lngRange += 360; } return { lat: minLat + Math.random() * latRange, lng: minLng + Math.random() * lngRange, }; } const total = 100; const intersectionObserver = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { entry.target.classList.add('drop'); intersectionObserver.unobserve(entry.target); } } }); async function initMap(): Promise<void> { // Request needed libraries. const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary; const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary; const position = {lat: 37.4242011827985, lng: -122.09242296450893}; const map = new Map(document.getElementById("map") as HTMLElement, { zoom: 14, center: position, mapId: '4504f8b37365c3d0', }); // Create 100 markers to animate. google.maps.event.addListenerOnce(map, 'idle', () => { for (let i = 0; i < 100; i++) { createMarker(map, AdvancedMarkerElement); } }); // Add a button to reset the example. const controlDiv = document.createElement("div"); const controlUI = document.createElement("button"); controlUI.classList.add("ui-button"); controlUI.innerText = "Reset the example"; controlUI.addEventListener("click", () => { // Reset the example by reloading the map iframe. refreshMap(); }); controlDiv.appendChild(controlUI); map.controls[google.maps.ControlPosition.TOP_CENTER].push(controlDiv); } function createMarker(map, AdvancedMarkerElement) { const advancedMarker = new AdvancedMarkerElement({ position: getRandomPosition(map), map: map, }); const content = advancedMarker.content as HTMLElement; content.style.opacity = '0'; content.addEventListener('animationend', (event) => { content.classList.remove('drop'); content.style.opacity = '1'; }); const time = 2 + Math.random(); // 2s delay for easy to see the animation content.style.setProperty('--delay-time', time +'s'); intersectionObserver.observe(content); } function refreshMap() { // Refresh the map. const mapContainer = document.getElementById('mapContainer'); const map = document.getElementById('map'); map!.remove(); const mapDiv = document.createElement('div'); mapDiv.id = 'map'; mapContainer!.appendChild(mapDiv); initMap(); } initMap();
JavaScript
/** * Returns a random lat lng position within the map bounds. * @param {!google.maps.Map} map * @return {!google.maps.LatLngLiteral} */ function getRandomPosition(map) { const bounds = map.getBounds(); const minLat = bounds.getSouthWest().lat(); const minLng = bounds.getSouthWest().lng(); const maxLat = bounds.getNorthEast().lat(); const maxLng = bounds.getNorthEast().lng(); const latRange = maxLat - minLat; // Note: longitude can span from a positive longitude in the west to a // negative one in the east. e.g. 150lng (150E) <-> -30lng (30W) is a large // span that covers the whole USA. let lngRange = maxLng - minLng; if (maxLng < minLng) { lngRange += 360; } return { lat: minLat + Math.random() * latRange, lng: minLng + Math.random() * lngRange, }; } const total = 100; const intersectionObserver = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { entry.target.classList.add("drop"); intersectionObserver.unobserve(entry.target); } } }); async function initMap() { // Request needed libraries. const { Map } = await google.maps.importLibrary("maps"); const { AdvancedMarkerElement } = await google.maps.importLibrary("marker"); const position = { lat: 37.4242011827985, lng: -122.09242296450893 }; const map = new Map(document.getElementById("map"), { zoom: 14, center: position, mapId: "4504f8b37365c3d0", }); // Create 100 markers to animate. google.maps.event.addListenerOnce(map, "idle", () => { for (let i = 0; i < 100; i++) { createMarker(map, AdvancedMarkerElement); } }); // Add a button to reset the example. const controlDiv = document.createElement("div"); const controlUI = document.createElement("button"); controlUI.classList.add("ui-button"); controlUI.innerText = "Reset the example"; controlUI.addEventListener("click", () => { // Reset the example by reloading the map iframe. refreshMap(); }); controlDiv.appendChild(controlUI); map.controls[google.maps.ControlPosition.TOP_CENTER].push(controlDiv); } function createMarker(map, AdvancedMarkerElement) { const advancedMarker = new AdvancedMarkerElement({ position: getRandomPosition(map), map: map, }); const content = advancedMarker.content; content.style.opacity = "0"; content.addEventListener("animationend", (event) => { content.classList.remove("drop"); content.style.opacity = "1"; }); const time = 2 + Math.random(); // 2s delay for easy to see the animation content.style.setProperty("--delay-time", time + "s"); intersectionObserver.observe(content); } function refreshMap() { // Refresh the map. const mapContainer = document.getElementById("mapContainer"); const map = document.getElementById("map"); map.remove(); const mapDiv = document.createElement("div"); mapDiv.id = "map"; mapContainer.appendChild(mapDiv); initMap(); } initMap();
CSS
/* * Always set the map height explicitly to define the size of the div element * that contains the map. */ #map { height: 100%; } /* * Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } /* set the default transition time */ :root { --delay-time: .5s; } #map { height: 100%; } #mapContainer { height: 100%; } html, body { height: 100%; margin: 0; padding: 0; } @keyframes drop { 0% { transform: translateY(-200px) scaleY(0.9); opacity: 0; } 5% { opacity: 0.7; } 50% { transform: translateY(0px) scaleY(1); opacity: 1; } 65% { transform: translateY(-17px) scaleY(0.9); opacity: 1; } 75% { transform: translateY(-22px) scaleY(0.9); opacity: 1; } 100% { transform: translateY(0px) scaleY(1); opacity: 1; } } .drop { animation: drop 0.3s linear forwards var(--delay-time); } .ui-button { background-color: #fff; border: 0; border-radius: 2px; box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3); margin: 10px; padding: 0 0.5em; font: 400 18px Roboto, Arial, sans-serif; overflow: hidden; height: 40px; cursor: pointer; } .ui-button:hover { background: rgb(235, 235, 235); }
HTML
<html> <head> <title>Advanced Markers CSS Animation</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div id="mapContainer"> <div id="map" style="height: 100%"></div> </div> <!-- prettier-ignore --> <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) ({key: "AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg", v: "weekly"});</script> </body> </html>