From cfbc063ea90da32d2fae25b8726a0e9a95214435 Mon Sep 17 00:00:00 2001 From: William French Date: Fri, 5 Dec 2025 12:23:38 -0800 Subject: [PATCH 01/11] feat: Adds new samples for AI-powered summaries. --- samples/ai-powered-summaries-basic/README.md | 36 +++ samples/ai-powered-summaries-basic/index.html | 26 ++ samples/ai-powered-summaries-basic/index.ts | 83 ++++++ .../ai-powered-summaries-basic/package.json | 14 + samples/ai-powered-summaries-basic/style.css | 25 ++ .../ai-powered-summaries-basic/tsconfig.json | 17 ++ samples/ai-powered-summaries/README.md | 36 +++ samples/ai-powered-summaries/index.html | 48 ++++ samples/ai-powered-summaries/index.ts | 240 ++++++++++++++++++ samples/ai-powered-summaries/package.json | 14 + samples/ai-powered-summaries/style.css | 112 ++++++++ samples/ai-powered-summaries/tsconfig.json | 17 ++ 12 files changed, 668 insertions(+) create mode 100644 samples/ai-powered-summaries-basic/README.md create mode 100644 samples/ai-powered-summaries-basic/index.html create mode 100644 samples/ai-powered-summaries-basic/index.ts create mode 100644 samples/ai-powered-summaries-basic/package.json create mode 100644 samples/ai-powered-summaries-basic/style.css create mode 100644 samples/ai-powered-summaries-basic/tsconfig.json create mode 100644 samples/ai-powered-summaries/README.md create mode 100644 samples/ai-powered-summaries/index.html create mode 100644 samples/ai-powered-summaries/index.ts create mode 100644 samples/ai-powered-summaries/package.json create mode 100644 samples/ai-powered-summaries/style.css create mode 100644 samples/ai-powered-summaries/tsconfig.json diff --git a/samples/ai-powered-summaries-basic/README.md b/samples/ai-powered-summaries-basic/README.md new file mode 100644 index 00000000..1a4b6847 --- /dev/null +++ b/samples/ai-powered-summaries-basic/README.md @@ -0,0 +1,36 @@ +# Google Maps JavaScript Sample + +## ai-powered-summaries-basic + +The ai-powered-summaries-basic sample demonstrates how to retrieve AI-powered summaries. + +Follow these instructions to set up and run ai-powered-summaries-basic sample on your local computer. + +## Setup + +### Before starting run: + +`npm i` + +### Run an example on a local web server + +First `cd` to the folder for the sample to run, then: + +`npm start` + +### Build an individual example + +From `samples/`: + +`npm run build --workspace=ai-powered-summaries-basic/` + +### Build all of the examples. + +From `samples/`: + +`npm run build-all` + +## Feedback + +For feedback related to this sample, please open a new issue on +[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). diff --git a/samples/ai-powered-summaries-basic/index.html b/samples/ai-powered-summaries-basic/index.html new file mode 100644 index 00000000..6def208f --- /dev/null +++ b/samples/ai-powered-summaries-basic/index.html @@ -0,0 +1,26 @@ + + + + + + AI-powered Summaries Basic Sample + + + + + + + + + + + + diff --git a/samples/ai-powered-summaries-basic/index.ts b/samples/ai-powered-summaries-basic/index.ts new file mode 100644 index 00000000..b659e0b9 --- /dev/null +++ b/samples/ai-powered-summaries-basic/index.ts @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START maps_ai_powered_summaries_basic] +const mapElement = document.querySelector('gmp-map') as google.maps.MapElement; +let innerMap; +let infoWindow; + +async function initMap() { + const { Map, InfoWindow } = (await google.maps.importLibrary( + 'maps' + )) as google.maps.MapsLibrary; + + innerMap = mapElement.innerMap; + infoWindow = new InfoWindow(); + getPlaceDetails(); +} + +async function getPlaceDetails() { + // Request needed libraries. + const [ {AdvancedMarkerElement}, { Place } ] = await Promise.all([ + google.maps.importLibrary('marker') as Promise, + google.maps.importLibrary('places') as Promise, + ]); + + // [START maps_ai_powered_summaries_basic_placeid] + // Use place ID to create a new Place instance. + const place = new Place({ + id: 'ChIJzzc-aWUM3IARPOQr9sA6vfY', // San Diego Botanic Garden + }); + // [END maps_ai_powered_summaries_basic_placeid] + + // Call fetchFields, passing the needed data fields. + // [START maps_ai_powered_summaries_basic_fetchfields] + await place.fetchFields({ + fields: [ + 'displayName', + 'formattedAddress', + 'location', + 'generativeSummary', + ], + }); + // [END maps_ai_powered_summaries_basic_fetchfields] + + // Add an Advanced Marker + const marker = new AdvancedMarkerElement({ + map: innerMap, + position: place.location, + title: place.displayName, + }); + + // Create a content container. + const content = document.createElement('div'); + // Populate the container with data. + const address = document.createElement('div'); + const summary = document.createElement('div'); + const lineBreak = document.createElement('br'); + + // Retrieve the summary text and disclosure text. + //@ts-ignore + let overviewText = place.generativeSummary.overview ?? 'No summary is available.'; + //@ts-ignore + let disclosureText = place.generativeSummary.disclosureText ?? ''; + + address.textContent = place.formattedAddress ?? ''; + summary.textContent = `${overviewText} [${disclosureText}]`; + content.append(address, lineBreak, summary);; + + innerMap.setCenter(place.location); + + // Display an info window. + infoWindow.setHeaderContent(place.displayName); + infoWindow.setContent(content); + infoWindow.open({ + anchor: marker, + }); +} + +initMap(); +// [END maps_ai_powered_summaries_basic] diff --git a/samples/ai-powered-summaries-basic/package.json b/samples/ai-powered-summaries-basic/package.json new file mode 100644 index 00000000..06aa6790 --- /dev/null +++ b/samples/ai-powered-summaries-basic/package.json @@ -0,0 +1,14 @@ +{ + "name": "@js-api-samples/ai-powered-summaries-basic", + "version": "1.0.0", + "scripts": { + "build": "tsc && bash ../jsfiddle.sh ai-powered-summaries-basic && bash ../app.sh ai-powered-summaries-basic && bash ../docs.sh ai-powered-summaries-basic && npm run build:vite --workspace=. && bash ../dist.sh ai-powered-summaries-basic", + "test": "tsc && npm run build:vite --workspace=.", + "start": "tsc && vite build --base './' && vite", + "build:vite": "vite build --base './'", + "preview": "vite preview" + }, + "dependencies": { + + } +} diff --git a/samples/ai-powered-summaries-basic/style.css b/samples/ai-powered-summaries-basic/style.css new file mode 100644 index 00000000..93167d23 --- /dev/null +++ b/samples/ai-powered-summaries-basic/style.css @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_ai_powered_summaries_basic] */ +/* + * 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; +} + +/* [END maps_ai_powered_summaries_basic] */ diff --git a/samples/ai-powered-summaries-basic/tsconfig.json b/samples/ai-powered-summaries-basic/tsconfig.json new file mode 100644 index 00000000..366aabb0 --- /dev/null +++ b/samples/ai-powered-summaries-basic/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "strict": true, + "noImplicitAny": false, + "lib": [ + "es2015", + "esnext", + "es6", + "dom", + "dom.iterable" + ], + "moduleResolution": "Node", + "jsx": "preserve" + } +} diff --git a/samples/ai-powered-summaries/README.md b/samples/ai-powered-summaries/README.md new file mode 100644 index 00000000..709d9585 --- /dev/null +++ b/samples/ai-powered-summaries/README.md @@ -0,0 +1,36 @@ +# Google Maps JavaScript Sample + +## ai-powered-summaries + +The ai-powered-summaries sample demonstrates how to show AI-powered summaries a map. + +Follow these instructions to set up and run ai-powered-summaries sample on your local computer. + +## Setup + +### Before starting run: + +`npm i` + +### Run an example on a local web server + +First `cd` to the folder for the sample to run, then: + +`npm start` + +### Build an individual example + +From `samples/`: + +`npm run build --workspace=ai-powered-summaries/` + +### Build all of the examples. + +From `samples/`: + +`npm run build-all` + +## Feedback + +For feedback related to this sample, please open a new issue on +[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). diff --git a/samples/ai-powered-summaries/index.html b/samples/ai-powered-summaries/index.html new file mode 100644 index 00000000..23437db0 --- /dev/null +++ b/samples/ai-powered-summaries/index.html @@ -0,0 +1,48 @@ + + + + + + AI Place Summaries + + + + + + + +
+

Search for a place with AI summaries:

+ +
+ + + +
+ + + diff --git a/samples/ai-powered-summaries/index.ts b/samples/ai-powered-summaries/index.ts new file mode 100644 index 00000000..25c33631 --- /dev/null +++ b/samples/ai-powered-summaries/index.ts @@ -0,0 +1,240 @@ +/* + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START maps_ai_powered_summaries] +// Define DOM elements. +const mapElement = document.querySelector('gmp-map') as google.maps.MapElement; +const placeAutocomplete = document.querySelector( + 'gmp-place-autocomplete' +) as google.maps.places.PlaceAutocompleteElement; +const summaryPanel = document.getElementById('summary-panel') as HTMLDivElement; +const placeName = document.getElementById('place-name') as HTMLElement; +const placeAddress = document.getElementById('place-address') as HTMLElement; +const tabContainer = document.getElementById('tab-container') as HTMLDivElement; +const summaryContent = document.getElementById( + 'summary-content' +) as HTMLDivElement; +const aiDisclosure = document.getElementById('ai-disclosure') as HTMLDivElement; + +let innerMap; +let marker: google.maps.marker.AdvancedMarkerElement; + +async function initMap(): Promise { + // Request needed libraries. + const [] = await Promise.all([ + google.maps.importLibrary('marker'), + google.maps.importLibrary('places'), + ]); + + innerMap = mapElement.innerMap; + innerMap.setOptions({ + mapTypeControl: false, + }); + + // Bind autocomplete bounds to map bounds. + google.maps.event.addListener(innerMap, 'bounds_changed', async () => { + placeAutocomplete.locationRestriction = innerMap.getBounds(); + }); + + // Create the marker. + marker = new google.maps.marker.AdvancedMarkerElement({ + map: innerMap, + }); + + // Handle selection of an autocomplete result. + // prettier-ignore + // @ts-ignore + placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => { + const place = placePrediction.toPlace(); + + // Fetch all summary fields. + // [START maps_ai_powered_summaries_fetchfields] + await place.fetchFields({ + fields: [ + 'displayName', + 'formattedAddress', + 'location', + 'generativeSummary', + 'neighborhoodSummary', + 'reviewSummary', + 'evChargeAmenitySummary', + ], + }); + // [END maps_ai_powered_summaries_fetchfields] + + // Update the map viewport and position the marker. + if (place.viewport) { + innerMap.fitBounds(place.viewport); + } else { + innerMap.setCenter(place.location); + innerMap.setZoom(17); + } + marker.position = place.location; + + // Update the panel UI. + updateSummaryPanel(place); + } + ); +} + +function updateSummaryPanel(place: google.maps.places.Place) { + // Reset UI + summaryPanel.classList.remove('hidden'); + tabContainer.innerHTML = ''; // innerHTML is OK here since we're clearing known child elements. + summaryContent.textContent = ''; + aiDisclosure.textContent = ''; + + placeName.textContent = place.displayName || ''; + placeAddress.textContent = place.formattedAddress || ''; + + let firstTabActivated = false; + + /** + * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment). + */ + const createTab = ( + label: string, + content: string | Node, + disclosure: string + ) => { + const btn = document.createElement('button'); + btn.className = 'tab-button'; + btn.textContent = label; + + btn.onclick = () => { + // Do nothing if the tab is already active. + if (btn.classList.contains('active')) { + return; + } + + // Manage the active class state. + document + .querySelectorAll('.tab-button') + .forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + + if (typeof content === 'string') { + summaryContent.textContent = content; + } else { + summaryContent.replaceChildren(content.cloneNode(true)); + } + + // Set the disclosure text. + aiDisclosure.textContent = disclosure || 'AI-generated content.'; + }; + + tabContainer.appendChild(btn); + + // Auto-select the first available summary. + if (!firstTabActivated) { + btn.click(); + firstTabActivated = true; + } + }; + + // --- 1. Generative Summary (Place) --- + //@ts-ignore + if (place.generativeSummary?.overview) { + createTab( + 'Overview', + //@ts-ignore + place.generativeSummary.overview, + //@ts-ignore + place.generativeSummary.disclosureText + ); + } + + // --- 2. Neighborhood Summary --- + //@ts-ignore + if (place.neighborhoodSummary?.overview?.content) { + createTab( + 'Neighborhood', + //@ts-ignore + place.neighborhoodSummary.overview.content, + //@ts-ignore + place.neighborhoodSummary.disclosureText + ); + } + + // --- 3. Review Summary --- + //@ts-ignore + if (place.reviewSummary?.text) { + createTab( + 'Reviews', + //@ts-ignore + place.reviewSummary.text, + //@ts-ignore + place.reviewSummary.disclosureText + ); + } + + // --- 4. EV Amenity Summary (uses content blocks)) --- + //@ts-ignore + if (place.evChargeAmenitySummary) { + //@ts-ignore + const evSummary = place.evChargeAmenitySummary; + const evContainer = document.createDocumentFragment(); + + // Helper to build a safe DOM section for EV categories. + const createSection = (title: string, text: string) => { + const wrapper = document.createElement('div'); + wrapper.style.marginBottom = '15px'; // Or use a CSS class + + const titleEl = document.createElement('strong'); + titleEl.textContent = title; + + const textEl = document.createElement('div'); + textEl.textContent = text; + + wrapper.appendChild(titleEl); + wrapper.appendChild(textEl); + return wrapper; + }; + + // Check and append each potential section + if (evSummary.overview?.content) { + evContainer.appendChild( + createSection('Overview', evSummary.overview.content) + ); + } + if (evSummary.coffee?.content) { + evContainer.appendChild( + createSection('Coffee', evSummary.coffee.content) + ); + } + if (evSummary.restaurant?.content) { + evContainer.appendChild( + createSection('Food', evSummary.restaurant.content) + ); + } + if (evSummary.store?.content) { + evContainer.appendChild( + createSection('Shopping', evSummary.store.content) + ); + } + + // Only add the tab if the container has children + if (evContainer.hasChildNodes()) { + createTab( + 'EV Amenities', + evContainer, // Passing a Node instead of string + evSummary.disclosureText + ); + } + } + + // Safely handle the empty state. + if (!firstTabActivated) { + const msg = document.createElement('em'); + msg.textContent = + 'No AI summaries are available for this specific location.'; + summaryContent.replaceChildren(msg); + aiDisclosure.textContent = ''; + } +} + +initMap(); +// [END maps_ai_powered_summaries] diff --git a/samples/ai-powered-summaries/package.json b/samples/ai-powered-summaries/package.json new file mode 100644 index 00000000..e9df893e --- /dev/null +++ b/samples/ai-powered-summaries/package.json @@ -0,0 +1,14 @@ +{ + "name": "@js-api-samples/ai-powered-summaries", + "version": "1.0.0", + "scripts": { + "build": "tsc && bash ../jsfiddle.sh ai-powered-summaries && bash ../app.sh ai-powered-summaries && bash ../docs.sh ai-powered-summaries && npm run build:vite --workspace=. && bash ../dist.sh ai-powered-summaries", + "test": "tsc && npm run build:vite --workspace=.", + "start": "tsc && vite build --base './' && vite", + "build:vite": "vite build --base './'", + "preview": "vite preview" + }, + "dependencies": { + + } +} diff --git a/samples/ai-powered-summaries/style.css b/samples/ai-powered-summaries/style.css new file mode 100644 index 00000000..d3a5f4ed --- /dev/null +++ b/samples/ai-powered-summaries/style.css @@ -0,0 +1,112 @@ +/** + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_ai_powered_summaries] */ +/* Reuse existing map height */ +gmp-map { + height: 100%; +} + +html, +body { + height: 100%; + margin: 0; + padding: 0; +} + +/* Existing Autocomplete Card Style */ +.place-autocomplete-card { + background-color: #fff; + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; + margin: 10px; + padding: 15px; + font-family: Roboto, sans-serif; + font-size: 1rem; +} + +gmp-place-autocomplete { + width: 300px; +} + +/* New: Summary Panel Styles */ +.summary-card { + background-color: #fff; + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; + margin: 10px; + padding: 0; /* Padding handled by children */ + font-family: Roboto, sans-serif; + width: 350px; + max-height: 80vh; /* Prevent overflow on small screens */ + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.hidden { + display: none; +} + +#place-header { + padding: 15px; + background-color: #f8f9fa; + border-bottom: 1px solid #ddd; +} + +#place-header h2 { + margin: 0 0 5px 0; + font-size: 1.2rem; +} + +#place-address { + margin: 0; + color: #555; + font-size: 0.9rem; +} + +/* Tab Navigation */ +.tab-container { + display: flex; + border-bottom: 1px solid #ddd; + background-color: #fff; +} + +.tab-button { + flex: 1; + background: none; + border: none; + padding: 10px; + cursor: pointer; + font-weight: 500; + color: #555; + border-bottom: 3px solid transparent; +} + +.tab-button:hover { + background-color: #f1f1f1; +} + +.tab-button.active { + color: #1a73e8; + border-bottom: 3px solid #1a73e8; +} + +/* Content Area */ +.content-area { + padding: 15px; + line-height: 1.5; + font-size: 0.95rem; + color: #333; +} + +.disclosure-footer { + font-size: 0.75rem; + color: #666; + padding: 10px 15px; + border-top: 1px solid #eee; + font-style: italic; +} +/* [END maps_ai_powered_summaries] */ diff --git a/samples/ai-powered-summaries/tsconfig.json b/samples/ai-powered-summaries/tsconfig.json new file mode 100644 index 00000000..366aabb0 --- /dev/null +++ b/samples/ai-powered-summaries/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "strict": true, + "noImplicitAny": false, + "lib": [ + "es2015", + "esnext", + "es6", + "dom", + "dom.iterable" + ], + "moduleResolution": "Node", + "jsx": "preserve" + } +} From 84201425799b0639ca7acc11b2ed7a73fa6c4817 Mon Sep 17 00:00:00 2001 From: William French Date: Wed, 10 Dec 2025 10:58:11 -0800 Subject: [PATCH 02/11] Swap order of neighborhood and review summary sections --- samples/ai-powered-summaries/index.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/samples/ai-powered-summaries/index.ts b/samples/ai-powered-summaries/index.ts index 25c33631..b6917fae 100644 --- a/samples/ai-powered-summaries/index.ts +++ b/samples/ai-powered-summaries/index.ts @@ -147,27 +147,27 @@ function updateSummaryPanel(place: google.maps.places.Place) { ); } - // --- 2. Neighborhood Summary --- + // --- 2. Review Summary --- //@ts-ignore - if (place.neighborhoodSummary?.overview?.content) { + if (place.reviewSummary?.text) { createTab( - 'Neighborhood', + 'Reviews', //@ts-ignore - place.neighborhoodSummary.overview.content, + place.reviewSummary.text, //@ts-ignore - place.neighborhoodSummary.disclosureText + place.reviewSummary.disclosureText ); } - - // --- 3. Review Summary --- + + // --- 3. Neighborhood Summary --- //@ts-ignore - if (place.reviewSummary?.text) { + if (place.neighborhoodSummary?.overview?.content) { createTab( - 'Reviews', + 'Neighborhood', //@ts-ignore - place.reviewSummary.text, + place.neighborhoodSummary.overview.content, //@ts-ignore - place.reviewSummary.disclosureText + place.neighborhoodSummary.disclosureText ); } From efa455937aa62e65208f6ddbbb09599725564905 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 09:13:46 -0800 Subject: [PATCH 03/11] Disable StreetView and FullScreen UI controls --- samples/ai-powered-summaries-basic/index.ts | 273 ++++++++++++++++---- 1 file changed, 216 insertions(+), 57 deletions(-) diff --git a/samples/ai-powered-summaries-basic/index.ts b/samples/ai-powered-summaries-basic/index.ts index b659e0b9..56cc9296 100644 --- a/samples/ai-powered-summaries-basic/index.ts +++ b/samples/ai-powered-summaries-basic/index.ts @@ -1,83 +1,242 @@ -/** +/* * @license * Copyright 2025 Google LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -// [START maps_ai_powered_summaries_basic] +// [START maps_ai_powered_summaries] +// Define DOM elements. const mapElement = document.querySelector('gmp-map') as google.maps.MapElement; -let innerMap; -let infoWindow; - -async function initMap() { - const { Map, InfoWindow } = (await google.maps.importLibrary( - 'maps' - )) as google.maps.MapsLibrary; +const placeAutocomplete = document.querySelector( + 'gmp-place-autocomplete' +) as google.maps.places.PlaceAutocompleteElement; +const summaryPanel = document.getElementById('summary-panel') as HTMLDivElement; +const placeName = document.getElementById('place-name') as HTMLElement; +const placeAddress = document.getElementById('place-address') as HTMLElement; +const tabContainer = document.getElementById('tab-container') as HTMLDivElement; +const summaryContent = document.getElementById( + 'summary-content' +) as HTMLDivElement; +const aiDisclosure = document.getElementById('ai-disclosure') as HTMLDivElement; - innerMap = mapElement.innerMap; - infoWindow = new InfoWindow(); - getPlaceDetails(); -} +let innerMap; +let marker: google.maps.marker.AdvancedMarkerElement; -async function getPlaceDetails() { +async function initMap(): Promise { // Request needed libraries. - const [ {AdvancedMarkerElement}, { Place } ] = await Promise.all([ - google.maps.importLibrary('marker') as Promise, - google.maps.importLibrary('places') as Promise, + const [] = await Promise.all([ + google.maps.importLibrary('marker'), + google.maps.importLibrary('places'), ]); - // [START maps_ai_powered_summaries_basic_placeid] - // Use place ID to create a new Place instance. - const place = new Place({ - id: 'ChIJzzc-aWUM3IARPOQr9sA6vfY', // San Diego Botanic Garden + innerMap = mapElement.innerMap; + innerMap.setOptions({ + mapTypeControl: false, + streetViewControl: false, + fullscreenControl: false, }); - // [END maps_ai_powered_summaries_basic_placeid] - - // Call fetchFields, passing the needed data fields. - // [START maps_ai_powered_summaries_basic_fetchfields] - await place.fetchFields({ - fields: [ - 'displayName', - 'formattedAddress', - 'location', - 'generativeSummary', - ], + + // Bind autocomplete bounds to map bounds. + google.maps.event.addListener(innerMap, 'bounds_changed', async () => { + placeAutocomplete.locationRestriction = innerMap.getBounds(); }); - // [END maps_ai_powered_summaries_basic_fetchfields] - // Add an Advanced Marker - const marker = new AdvancedMarkerElement({ + // Create the marker. + marker = new google.maps.marker.AdvancedMarkerElement({ map: innerMap, - position: place.location, - title: place.displayName, }); - // Create a content container. - const content = document.createElement('div'); - // Populate the container with data. - const address = document.createElement('div'); - const summary = document.createElement('div'); - const lineBreak = document.createElement('br'); + // Handle selection of an autocomplete result. + // prettier-ignore + // @ts-ignore + placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => { + const place = placePrediction.toPlace(); + + // Fetch all summary fields. + // [START maps_ai_powered_summaries_fetchfields] + await place.fetchFields({ + fields: [ + 'displayName', + 'formattedAddress', + 'location', + 'generativeSummary', + 'neighborhoodSummary', + 'reviewSummary', + 'evChargeAmenitySummary', + ], + }); + // [END maps_ai_powered_summaries_fetchfields] + + // Update the map viewport and position the marker. + if (place.viewport) { + innerMap.fitBounds(place.viewport); + } else { + innerMap.setCenter(place.location); + innerMap.setZoom(17); + } + marker.position = place.location; + + // Update the panel UI. + updateSummaryPanel(place); + } + ); +} + +function updateSummaryPanel(place: google.maps.places.Place) { + // Reset UI + summaryPanel.classList.remove('hidden'); + tabContainer.innerHTML = ''; // innerHTML is OK here since we're clearing known child elements. + summaryContent.textContent = ''; + aiDisclosure.textContent = ''; + + placeName.textContent = place.displayName || ''; + placeAddress.textContent = place.formattedAddress || ''; + + let firstTabActivated = false; + + /** + * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment). + */ + const createTab = ( + label: string, + content: string | Node, + disclosure: string + ) => { + const btn = document.createElement('button'); + btn.className = 'tab-button'; + btn.textContent = label; + + btn.onclick = () => { + // Do nothing if the tab is already active. + if (btn.classList.contains('active')) { + return; + } - // Retrieve the summary text and disclosure text. + // Manage the active class state. + document + .querySelectorAll('.tab-button') + .forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + + if (typeof content === 'string') { + summaryContent.textContent = content; + } else { + summaryContent.replaceChildren(content.cloneNode(true)); + } + + // Set the disclosure text. + aiDisclosure.textContent = disclosure || 'AI-generated content.'; + }; + + tabContainer.appendChild(btn); + + // Auto-select the first available summary. + if (!firstTabActivated) { + btn.click(); + firstTabActivated = true; + } + }; + + // --- 1. Generative Summary (Place) --- //@ts-ignore - let overviewText = place.generativeSummary.overview ?? 'No summary is available.'; + if (place.generativeSummary?.overview) { + createTab( + 'Overview', + //@ts-ignore + place.generativeSummary.overview, + //@ts-ignore + place.generativeSummary.disclosureText + ); + } + + // --- 2. Review Summary --- //@ts-ignore - let disclosureText = place.generativeSummary.disclosureText ?? ''; + if (place.reviewSummary?.text) { + createTab( + 'Reviews', + //@ts-ignore + place.reviewSummary.text, + //@ts-ignore + place.reviewSummary.disclosureText + ); + } - address.textContent = place.formattedAddress ?? ''; - summary.textContent = `${overviewText} [${disclosureText}]`; - content.append(address, lineBreak, summary);; + // --- 3. Neighborhood Summary --- + //@ts-ignore + if (place.neighborhoodSummary?.overview?.content) { + createTab( + 'Neighborhood', + //@ts-ignore + place.neighborhoodSummary.overview.content, + //@ts-ignore + place.neighborhoodSummary.disclosureText + ); + } - innerMap.setCenter(place.location); + // --- 4. EV Amenity Summary (uses content blocks)) --- + //@ts-ignore + if (place.evChargeAmenitySummary) { + //@ts-ignore + const evSummary = place.evChargeAmenitySummary; + const evContainer = document.createDocumentFragment(); - // Display an info window. - infoWindow.setHeaderContent(place.displayName); - infoWindow.setContent(content); - infoWindow.open({ - anchor: marker, - }); + // Helper to build a safe DOM section for EV categories. + const createSection = (title: string, text: string) => { + const wrapper = document.createElement('div'); + wrapper.style.marginBottom = '15px'; // Or use a CSS class + + const titleEl = document.createElement('strong'); + titleEl.textContent = title; + + const textEl = document.createElement('div'); + textEl.textContent = text; + + wrapper.appendChild(titleEl); + wrapper.appendChild(textEl); + return wrapper; + }; + + // Check and append each potential section + if (evSummary.overview?.content) { + evContainer.appendChild( + createSection('Overview', evSummary.overview.content) + ); + } + if (evSummary.coffee?.content) { + evContainer.appendChild( + createSection('Coffee', evSummary.coffee.content) + ); + } + if (evSummary.restaurant?.content) { + evContainer.appendChild( + createSection('Food', evSummary.restaurant.content) + ); + } + if (evSummary.store?.content) { + evContainer.appendChild( + createSection('Shopping', evSummary.store.content) + ); + } + + // Only add the tab if the container has children + if (evContainer.hasChildNodes()) { + createTab( + 'EV Amenities', + evContainer, // Passing a Node instead of string + evSummary.disclosureText + ); + } + } + + // Safely handle the empty state. + if (!firstTabActivated) { + const msg = document.createElement('em'); + msg.textContent = + 'No AI summaries are available for this specific location.'; + summaryContent.replaceChildren(msg); + aiDisclosure.textContent = ''; + } } initMap(); -// [END maps_ai_powered_summaries_basic] +// [END maps_ai_powered_summaries] From 5d97ed272a987275a6ea3f4da30aa42a7e647f4b Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 09:18:53 -0800 Subject: [PATCH 04/11] revert previous change I accidentally updated the wrong file --- samples/ai-powered-summaries-basic/index.ts | 273 ++++---------------- 1 file changed, 57 insertions(+), 216 deletions(-) diff --git a/samples/ai-powered-summaries-basic/index.ts b/samples/ai-powered-summaries-basic/index.ts index 56cc9296..b659e0b9 100644 --- a/samples/ai-powered-summaries-basic/index.ts +++ b/samples/ai-powered-summaries-basic/index.ts @@ -1,242 +1,83 @@ -/* +/** * @license * Copyright 2025 Google LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -// [START maps_ai_powered_summaries] -// Define DOM elements. +// [START maps_ai_powered_summaries_basic] const mapElement = document.querySelector('gmp-map') as google.maps.MapElement; -const placeAutocomplete = document.querySelector( - 'gmp-place-autocomplete' -) as google.maps.places.PlaceAutocompleteElement; -const summaryPanel = document.getElementById('summary-panel') as HTMLDivElement; -const placeName = document.getElementById('place-name') as HTMLElement; -const placeAddress = document.getElementById('place-address') as HTMLElement; -const tabContainer = document.getElementById('tab-container') as HTMLDivElement; -const summaryContent = document.getElementById( - 'summary-content' -) as HTMLDivElement; -const aiDisclosure = document.getElementById('ai-disclosure') as HTMLDivElement; - let innerMap; -let marker: google.maps.marker.AdvancedMarkerElement; +let infoWindow; + +async function initMap() { + const { Map, InfoWindow } = (await google.maps.importLibrary( + 'maps' + )) as google.maps.MapsLibrary; + + innerMap = mapElement.innerMap; + infoWindow = new InfoWindow(); + getPlaceDetails(); +} -async function initMap(): Promise { +async function getPlaceDetails() { // Request needed libraries. - const [] = await Promise.all([ - google.maps.importLibrary('marker'), - google.maps.importLibrary('places'), + const [ {AdvancedMarkerElement}, { Place } ] = await Promise.all([ + google.maps.importLibrary('marker') as Promise, + google.maps.importLibrary('places') as Promise, ]); - innerMap = mapElement.innerMap; - innerMap.setOptions({ - mapTypeControl: false, - streetViewControl: false, - fullscreenControl: false, + // [START maps_ai_powered_summaries_basic_placeid] + // Use place ID to create a new Place instance. + const place = new Place({ + id: 'ChIJzzc-aWUM3IARPOQr9sA6vfY', // San Diego Botanic Garden }); - - // Bind autocomplete bounds to map bounds. - google.maps.event.addListener(innerMap, 'bounds_changed', async () => { - placeAutocomplete.locationRestriction = innerMap.getBounds(); + // [END maps_ai_powered_summaries_basic_placeid] + + // Call fetchFields, passing the needed data fields. + // [START maps_ai_powered_summaries_basic_fetchfields] + await place.fetchFields({ + fields: [ + 'displayName', + 'formattedAddress', + 'location', + 'generativeSummary', + ], }); + // [END maps_ai_powered_summaries_basic_fetchfields] - // Create the marker. - marker = new google.maps.marker.AdvancedMarkerElement({ + // Add an Advanced Marker + const marker = new AdvancedMarkerElement({ map: innerMap, + position: place.location, + title: place.displayName, }); - // Handle selection of an autocomplete result. - // prettier-ignore - // @ts-ignore - placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => { - const place = placePrediction.toPlace(); - - // Fetch all summary fields. - // [START maps_ai_powered_summaries_fetchfields] - await place.fetchFields({ - fields: [ - 'displayName', - 'formattedAddress', - 'location', - 'generativeSummary', - 'neighborhoodSummary', - 'reviewSummary', - 'evChargeAmenitySummary', - ], - }); - // [END maps_ai_powered_summaries_fetchfields] - - // Update the map viewport and position the marker. - if (place.viewport) { - innerMap.fitBounds(place.viewport); - } else { - innerMap.setCenter(place.location); - innerMap.setZoom(17); - } - marker.position = place.location; - - // Update the panel UI. - updateSummaryPanel(place); - } - ); -} - -function updateSummaryPanel(place: google.maps.places.Place) { - // Reset UI - summaryPanel.classList.remove('hidden'); - tabContainer.innerHTML = ''; // innerHTML is OK here since we're clearing known child elements. - summaryContent.textContent = ''; - aiDisclosure.textContent = ''; - - placeName.textContent = place.displayName || ''; - placeAddress.textContent = place.formattedAddress || ''; - - let firstTabActivated = false; - - /** - * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment). - */ - const createTab = ( - label: string, - content: string | Node, - disclosure: string - ) => { - const btn = document.createElement('button'); - btn.className = 'tab-button'; - btn.textContent = label; - - btn.onclick = () => { - // Do nothing if the tab is already active. - if (btn.classList.contains('active')) { - return; - } + // Create a content container. + const content = document.createElement('div'); + // Populate the container with data. + const address = document.createElement('div'); + const summary = document.createElement('div'); + const lineBreak = document.createElement('br'); - // Manage the active class state. - document - .querySelectorAll('.tab-button') - .forEach((b) => b.classList.remove('active')); - btn.classList.add('active'); - - if (typeof content === 'string') { - summaryContent.textContent = content; - } else { - summaryContent.replaceChildren(content.cloneNode(true)); - } - - // Set the disclosure text. - aiDisclosure.textContent = disclosure || 'AI-generated content.'; - }; - - tabContainer.appendChild(btn); - - // Auto-select the first available summary. - if (!firstTabActivated) { - btn.click(); - firstTabActivated = true; - } - }; - - // --- 1. Generative Summary (Place) --- + // Retrieve the summary text and disclosure text. //@ts-ignore - if (place.generativeSummary?.overview) { - createTab( - 'Overview', - //@ts-ignore - place.generativeSummary.overview, - //@ts-ignore - place.generativeSummary.disclosureText - ); - } - - // --- 2. Review Summary --- + let overviewText = place.generativeSummary.overview ?? 'No summary is available.'; //@ts-ignore - if (place.reviewSummary?.text) { - createTab( - 'Reviews', - //@ts-ignore - place.reviewSummary.text, - //@ts-ignore - place.reviewSummary.disclosureText - ); - } + let disclosureText = place.generativeSummary.disclosureText ?? ''; - // --- 3. Neighborhood Summary --- - //@ts-ignore - if (place.neighborhoodSummary?.overview?.content) { - createTab( - 'Neighborhood', - //@ts-ignore - place.neighborhoodSummary.overview.content, - //@ts-ignore - place.neighborhoodSummary.disclosureText - ); - } + address.textContent = place.formattedAddress ?? ''; + summary.textContent = `${overviewText} [${disclosureText}]`; + content.append(address, lineBreak, summary);; - // --- 4. EV Amenity Summary (uses content blocks)) --- - //@ts-ignore - if (place.evChargeAmenitySummary) { - //@ts-ignore - const evSummary = place.evChargeAmenitySummary; - const evContainer = document.createDocumentFragment(); + innerMap.setCenter(place.location); - // Helper to build a safe DOM section for EV categories. - const createSection = (title: string, text: string) => { - const wrapper = document.createElement('div'); - wrapper.style.marginBottom = '15px'; // Or use a CSS class - - const titleEl = document.createElement('strong'); - titleEl.textContent = title; - - const textEl = document.createElement('div'); - textEl.textContent = text; - - wrapper.appendChild(titleEl); - wrapper.appendChild(textEl); - return wrapper; - }; - - // Check and append each potential section - if (evSummary.overview?.content) { - evContainer.appendChild( - createSection('Overview', evSummary.overview.content) - ); - } - if (evSummary.coffee?.content) { - evContainer.appendChild( - createSection('Coffee', evSummary.coffee.content) - ); - } - if (evSummary.restaurant?.content) { - evContainer.appendChild( - createSection('Food', evSummary.restaurant.content) - ); - } - if (evSummary.store?.content) { - evContainer.appendChild( - createSection('Shopping', evSummary.store.content) - ); - } - - // Only add the tab if the container has children - if (evContainer.hasChildNodes()) { - createTab( - 'EV Amenities', - evContainer, // Passing a Node instead of string - evSummary.disclosureText - ); - } - } - - // Safely handle the empty state. - if (!firstTabActivated) { - const msg = document.createElement('em'); - msg.textContent = - 'No AI summaries are available for this specific location.'; - summaryContent.replaceChildren(msg); - aiDisclosure.textContent = ''; - } + // Display an info window. + infoWindow.setHeaderContent(place.displayName); + infoWindow.setContent(content); + infoWindow.open({ + anchor: marker, + }); } initMap(); -// [END maps_ai_powered_summaries] +// [END maps_ai_powered_summaries_basic] From d09a1a8a0ebbd9e365ac9bd261ace0d604db07a0 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 09:19:56 -0800 Subject: [PATCH 05/11] Update map options to hide controls Disable street view and fullscreen controls on the map. --- samples/ai-powered-summaries/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/ai-powered-summaries/index.ts b/samples/ai-powered-summaries/index.ts index b6917fae..56cc9296 100644 --- a/samples/ai-powered-summaries/index.ts +++ b/samples/ai-powered-summaries/index.ts @@ -32,6 +32,8 @@ async function initMap(): Promise { innerMap = mapElement.innerMap; innerMap.setOptions({ mapTypeControl: false, + streetViewControl: false, + fullscreenControl: false, }); // Bind autocomplete bounds to map bounds. @@ -158,7 +160,7 @@ function updateSummaryPanel(place: google.maps.places.Place) { place.reviewSummary.disclosureText ); } - + // --- 3. Neighborhood Summary --- //@ts-ignore if (place.neighborhoodSummary?.overview?.content) { From cdf5a028aa7b0b01beac5dec0a50728533c49742 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 09:20:37 -0800 Subject: [PATCH 06/11] Modify active tab button styles in style.css Updated active tab button styles for better visibility. --- samples/ai-powered-summaries/style.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/samples/ai-powered-summaries/style.css b/samples/ai-powered-summaries/style.css index d3a5f4ed..a1ce9bb7 100644 --- a/samples/ai-powered-summaries/style.css +++ b/samples/ai-powered-summaries/style.css @@ -90,8 +90,13 @@ gmp-place-autocomplete { } .tab-button.active { - color: #1a73e8; - border-bottom: 3px solid #1a73e8; + font-weight: bold; + border-bottom: 3px solid #000000; +} + +.tab-button.active:hover { + background-color: #ffffff; + cursor: default; } /* Content Area */ From 5a05f02a646c47f7169b017a40c1346044b4734e Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 09:54:46 -0800 Subject: [PATCH 07/11] Modify map options and enhance place detail display * Adds click event for marker to display info window. * Adds HTML for "report an issue" link. --- samples/ai-powered-summaries-basic/index.ts | 35 ++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/samples/ai-powered-summaries-basic/index.ts b/samples/ai-powered-summaries-basic/index.ts index b659e0b9..58ab067d 100644 --- a/samples/ai-powered-summaries-basic/index.ts +++ b/samples/ai-powered-summaries-basic/index.ts @@ -15,6 +15,10 @@ async function initMap() { )) as google.maps.MapsLibrary; innerMap = mapElement.innerMap; + innerMap.setOptions({ + mapTypeControl: false + }); + infoWindow = new InfoWindow(); getPlaceDetails(); } @@ -58,19 +62,42 @@ async function getPlaceDetails() { const address = document.createElement('div'); const summary = document.createElement('div'); const lineBreak = document.createElement('br'); + const attribution = document.createElement('div'); - // Retrieve the summary text and disclosure text. + // Retrieve the textual data (summary, disclosure, flag URI). //@ts-ignore let overviewText = place.generativeSummary.overview ?? 'No summary is available.'; //@ts-ignore - let disclosureText = place.generativeSummary.disclosureText ?? ''; + let disclosureText = place.generativeSummary.disclosureText; + //@ts-ignore + let reportingUri = place.generativeSummary.flagContentURI; + // Create HTML for reporting link. + const reportingLink = document.createElement('a'); + reportingLink.href = reportingUri; + reportingLink.target = '_blank'; + reportingLink.textContent = "Report a problem." + + // Add text to layout. address.textContent = place.formattedAddress ?? ''; - summary.textContent = `${overviewText} [${disclosureText}]`; - content.append(address, lineBreak, summary);; + summary.textContent = overviewText; + attribution.textContent = `${disclosureText} `; + attribution.appendChild(reportingLink); + + content.append(address, lineBreak, summary, lineBreak, attribution); innerMap.setCenter(place.location); + // Handle marker click. + marker.addListener('gmp-click', () => { + showInfoWindow(marker, place, content); + }); + + // Display the info window at load time. + showInfoWindow(marker, place, content); +} + +function showInfoWindow(marker, place, content) { // Display an info window. infoWindow.setHeaderContent(place.displayName); infoWindow.setContent(content); From c67bf67dbce0bae059e38b48bbf114de5f78238a Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 12:23:28 -0800 Subject: [PATCH 08/11] Add flag content link to index.html Added a flag content link to the AI summaries page. --- samples/ai-powered-summaries/index.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/ai-powered-summaries/index.html b/samples/ai-powered-summaries/index.html index 23437db0..02da9252 100644 --- a/samples/ai-powered-summaries/index.html +++ b/samples/ai-powered-summaries/index.html @@ -9,7 +9,8 @@ AI Place Summaries - + + @@ -41,6 +42,9 @@

+ + + From 20e48c4d2efa51b29c587d6292e30e5fc61d5769 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 12:23:51 -0800 Subject: [PATCH 09/11] Add flag URL support to summary tabs --- samples/ai-powered-summaries/index.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/samples/ai-powered-summaries/index.ts b/samples/ai-powered-summaries/index.ts index 56cc9296..4c44a671 100644 --- a/samples/ai-powered-summaries/index.ts +++ b/samples/ai-powered-summaries/index.ts @@ -18,6 +18,7 @@ const summaryContent = document.getElementById( 'summary-content' ) as HTMLDivElement; const aiDisclosure = document.getElementById('ai-disclosure') as HTMLDivElement; +const flagContentLink = document.getElementById('flag-content-link') as HTMLAnchorElement; let innerMap; let marker: google.maps.marker.AdvancedMarkerElement; @@ -100,7 +101,8 @@ function updateSummaryPanel(place: google.maps.places.Place) { const createTab = ( label: string, content: string | Node, - disclosure: string + disclosure: string, + flagUrl: string ) => { const btn = document.createElement('button'); btn.className = 'tab-button'; @@ -126,6 +128,12 @@ function updateSummaryPanel(place: google.maps.places.Place) { // Set the disclosure text. aiDisclosure.textContent = disclosure || 'AI-generated content.'; + + // Add the content flag URI. + if (flagUrl) { + flagContentLink.href = flagUrl; + flagContentLink.textContent = "Report an issue" + } }; tabContainer.appendChild(btn); @@ -145,7 +153,9 @@ function updateSummaryPanel(place: google.maps.places.Place) { //@ts-ignore place.generativeSummary.overview, //@ts-ignore - place.generativeSummary.disclosureText + place.generativeSummary.disclosureText, + //@ts-ignore + place.generativeSummary.flagContentURI ); } @@ -157,7 +167,9 @@ function updateSummaryPanel(place: google.maps.places.Place) { //@ts-ignore place.reviewSummary.text, //@ts-ignore - place.reviewSummary.disclosureText + place.reviewSummary.disclosureText, + //@ts-ignore + place.reviewSummary.flagContentURI ); } @@ -169,7 +181,9 @@ function updateSummaryPanel(place: google.maps.places.Place) { //@ts-ignore place.neighborhoodSummary.overview.content, //@ts-ignore - place.neighborhoodSummary.disclosureText + place.neighborhoodSummary.disclosureText, + //@ts-ignore + place.neighborhoodSummary.flagContentURI ); } @@ -223,7 +237,8 @@ function updateSummaryPanel(place: google.maps.places.Place) { createTab( 'EV Amenities', evContainer, // Passing a Node instead of string - evSummary.disclosureText + evSummary.disclosureText, + evSummary.flagContentURI ); } } From d40ff099e34bfe2aeeae0fcbe60e91d7348a5420 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 11 Dec 2025 12:24:18 -0800 Subject: [PATCH 10/11] Add summary panel and tab navigation styles Added styles for the summary panel and tab navigation. --- samples/ai-powered-summaries/style.css | 131 +++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/samples/ai-powered-summaries/style.css b/samples/ai-powered-summaries/style.css index a1ce9bb7..e0ef4f30 100644 --- a/samples/ai-powered-summaries/style.css +++ b/samples/ai-powered-summaries/style.css @@ -114,4 +114,135 @@ gmp-place-autocomplete { border-top: 1px solid #eee; font-style: italic; } + +.flag-content-link { + font-size: 0.75rem; + color: #666; + padding: 10px 15px; + border-top: 1px solid #eee; +} +/* [END maps_ai_powered_summaries] */ +/** + * @license + * Copyright 2025 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* [START maps_ai_powered_summaries] */ +/* Reuse existing map height */ +gmp-map { + height: 100%; +} + +html, +body { + height: 100%; + margin: 0; + padding: 0; +} + +/* Existing Autocomplete Card Style */ +.place-autocomplete-card { + background-color: #fff; + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; + margin: 10px; + padding: 15px; + font-family: Roboto, sans-serif; + font-size: 1rem; +} + +gmp-place-autocomplete { + width: 300px; +} + +/* New: Summary Panel Styles */ +.summary-card { + background-color: #fff; + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; + margin: 10px; + padding: 0; /* Padding handled by children */ + font-family: Roboto, sans-serif; + width: 350px; + max-height: 80vh; /* Prevent overflow on small screens */ + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.hidden { + display: none; +} + +#place-header { + padding: 15px; + background-color: #f8f9fa; + border-bottom: 1px solid #ddd; +} + +#place-header h2 { + margin: 0 0 5px 0; + font-size: 1.2rem; +} + +#place-address { + margin: 0; + color: #555; + font-size: 0.9rem; +} + +/* Tab Navigation */ +.tab-container { + display: flex; + border-bottom: 1px solid #ddd; + background-color: #fff; +} + +.tab-button { + flex: 1; + background: none; + border: none; + padding: 10px; + cursor: pointer; + font-weight: 500; + color: #555; + border-bottom: 3px solid transparent; +} + +.tab-button:hover { + background-color: #f1f1f1; +} + +.tab-button.active { + font-weight: bold; + border-bottom: 3px solid #000000; +} + +.tab-button.active:hover { + background-color: #ffffff; + cursor: default; +} + +/* Content Area */ +.content-area { + padding: 15px; + line-height: 1.5; + font-size: 0.95rem; + color: #333; +} + +.disclosure-footer { + font-size: 0.75rem; + color: #666; + padding: 10px 15px; + border-top: 1px solid #eee; + font-style: italic; +} + +.flag-content-link { + font-size: 0.75rem; + color: #666; + padding: 10px 15px; + border-top: 1px solid #eee; +} /* [END maps_ai_powered_summaries] */ From 4761b9b00e7486d141a136cdcf5e43b6fccb1cfc Mon Sep 17 00:00:00 2001 From: William French Date: Fri, 12 Dec 2025 14:44:16 -0800 Subject: [PATCH 11/11] Remove border-top from flag-content-link style --- samples/ai-powered-summaries/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/ai-powered-summaries/style.css b/samples/ai-powered-summaries/style.css index e0ef4f30..31092e63 100644 --- a/samples/ai-powered-summaries/style.css +++ b/samples/ai-powered-summaries/style.css @@ -243,6 +243,5 @@ gmp-place-autocomplete { font-size: 0.75rem; color: #666; padding: 10px 15px; - border-top: 1px solid #eee; } /* [END maps_ai_powered_summaries] */