# Project export: Bridge AI: AI-Powered Social Welfare Locator

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: By just entering a ZIP code and a few clicks of a button, we guide unhoused and low-income folks to food, shelter, jobs — and hope.
- Devpost: https://devpost.com/software/bridge-h3pina
- GitHub: https://github.com/minggangli1030/bridge-hackathon-2025
- Video: https://www.youtube.com/embed/sDKSZrf4_oo?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Minggang Li (41 commits)

## Devpost submission (written by the team)

### Inspiration

Over the last half-century, financialization and globalization have fueled unprecedented GDP growth in the U.S. In 2024, U.S. GDP exceeded $28.3 trillion. However, because of financialization, the growth of high-tech industries, and globalization, wealth and income inequality have also widened significantly in the U.S. For instance, according to a UC Berkeley professor, wealth inequality in the U.S. has reached an all-time high (UC Berkeley). At the same time, Pew Research reports that income inequality is now at its highest level since 1928 (Pew Research). According to the Department of Housing and Urban Development’s annual report published in 2024, around 23 out of every 10,000 Americans—771,480 people—experienced homelessness in January 2024, and the total homeless population in the U.S. rose 32.5% from 2022 to 2024 (USAFacts). Currently, the U.S. has recorded 7.2 million unemployed people in May 2025 (U.S. Bureau of Labor Statistics), but only about 2 million claim unemployment insurance (United States Department of Labor). To alleviate poverty and reduce economic inequality, we have designed Bridge to help homeless and unemployed individuals find shelters, food banks, and employment services—assisting them in reengaging with the U.S. labor force, promoting social productivity, and enhancing societal security. What It Does By simply entering their ZIP code, users get easy access to rated resources with distances. There’s also a live AI ChatBot to interact and help consult on personal needs: ZIP Code Resource Search Instantly find nearby shelters, food banks, clinics, clothing closets, and more—complete with user ratings and distance from you. ZIP Code Resource Search Instantly find nearby shelters, food banks, clinics, clothing closets, and more—complete with user ratings and distance from you. Resource Map Interactive map plotting all free/low-cost services in your neighborhood—select an item in the list to highlight its pin on the map, and click a pin to spotlight its entry. Double-click a pin to open its location in Google Maps. Resource Map Interactive map plotting all free/low-cost services in your neighborhood—select an item in the list to highlight its pin on the map, and click a pin to spotlight its entry. Double-click a pin to open its location in Google Maps. Live AI ChatBot Chat in real time for personalized guidance and follow-ups, with seamless integration of ZIP-based weather and temperature data to tailor all recommendations. Live AI ChatBot Chat in real time for personalized guidance and follow-ups, with seamless integration of ZIP-based weather and temperature data to tailor all recommendations. How We Built It Anthropic-Powered LLM ChatBot Integration Fine-tuned large language models (via Claude API) drive our live chatbot for real-time, context-aware resource suggestions and user consultations. Anthropic-Powered LLM ChatBot Integration Fine-tuned large language models (via Claude API) drive our live chatbot for real-time, context-aware resource suggestions and user consultations. React Frontend + Node API Snappy React + Tailwind UI talks to an Express server that aggregates Google Places and community-sourced data. React Frontend + Node API Snappy React + Tailwind UI talks to an Express server that aggregates Google Places and community-sourced data. Google Maps APIs Geocoding API for converting ZIP code into lat/lng coords. Places API for finding nearby food banks, shelters, clinics, job centers, etc. Maps JavaScript API for rendering a visual map with interactive pins for each resource. Google Maps APIs Geocoding API for converting ZIP code into lat/lng coords. Places API for finding nearby food banks, shelters, clinics, job centers, etc. Maps JavaScript API for rendering a visual map with interactive pins for each resource. Weather-Aware Advice We hit the OpenWeatherMap API for current weather and temperature by ZIP code—so Claude can tailor its recommendations (“Bring a raincoat!”) to local conditions. Weather-Aware Advice We hit the OpenWeatherMap API for current weather and temperature by ZIP code—so Claude can tailor its recommendations (“Bring a raincoat!”) to local conditions. Modular AI Agents Separate brains for resource discovery, recommendation orchestration, and future profile modules—keeps things neat and scalable. Modular AI Agents Separate brains for resource discovery, recommendation orchestration, and future profile modules—keeps things neat and scalable. Challenges We Faced Keeping LLM outputs consistent meant endless prompt tuning and automated validation pipelines. Real-time queries across multiple agents pushed us to optimize caching, batching, and auto-scaling. Guarding against bias and protecting vulnerable users required strict safety nets and audit logs. Building trust meant partnering with local orgs and iterating the UX based on real feedback. Accomplishments We’re Proud Of Spun up three (Resource Discovery Agent, Recommendation Orchestrator, Profile & Follow-up Agent) specialized and interactive AI agents in just 24 hours. Rolled out a slick React UI with a super-simple workflow—“Enter ZIP → Pick need → Search”—so anyone can tap a few buttons to find help instantly. What We Learned AI can dramatically lower the barrier to vital services. By wrapping basic LLM and Google Maps API calls into a seamless AI agent, we make life-saving resources instantly accessible to those who need them most . For people with limited internet exposure and experience, we streamlined the flow to “Enter ZIP → Pick need → Go,” paired icons with clear labels, and enlarged tap targets—making the interface more accessible and intuitive. What’s Next for Bridge AI Collaborate with local and federal governments, as well as NGOs and social-welfare organizations, to expand free or low-cost shelters, food, clothing, technical-skills training, and other resources that meet our users’ needs. Launch native iOS and Android apps for broader accessibility and offline support. Create login features and a database to store user information, chat history, and query history. Analyzing our user data to help policymakers design better public and social welfare policies, including Universal Basic Income policies for local governments and the country. Expanding services to cover mental health counseling, community colleges, legal-aid referrals, and so on. Citations: Coy, Peter. “Wealth Inequality Is the Highest since World War II.” Wealth Inequality Is the Highest Since World War II | Letters & Science, 3 Feb. 2022, ls.berkeley.edu/news/wealth-inequality-highest-world-war-ii. DeSilver, Drew. “U.S. Income Inequality, on Rise for Decades, Is Now Highest since 1928.” Pew Research Center, Pew Research Center, 5 Dec. 2013, www.pewresearch.org/short-reads/2013/12/05/u-s-income-inequality-on-rise-for-decades-is-now-highest-since-1928/#:~:text=Emmanuel%20Saez%2C%20an%20economics%20professor,levels%20not%20seen%20since%201928. Kollar, Jessica Semega and Melissa. “Increase in Income Inequality Driven by Real Declines in Income at the Bottom.” Census.Gov, 13 Sept. 2022, www.census.gov/library/stories/2022/09/income-inequality-increased.html. “Labor Underutilization Measures Ranged from 1.5 Percent to 7.8 Percent in May 2025.” U.S. Bureau of Labor Statistics, U.S. Bureau of Labor Statistics, 17 June 2025, www.bls.gov/opub/ted/2025/labor-underutilization-measures-ranged-from-1-5-percent-to-7-8-percent-in-may-2025.htm. “UNEMPLOYMENT INSURANCE WEEKLY CLAIMS .” Office of Workforce Security, Employment & Training Administration (ETA) - U.S. Department of Labor, 18 June 2025, oui.doleta.gov/unemploy/archive.asp. USAFacts Team. “How Many Homeless People Are in the US? What Does the Data Miss?” USAFacts, USAFacts, 28 Feb. 2025, usafacts.org/articles/how-many-homeless-people-are-in-the-us-what-does-the-data-miss/.

## README (from the GitHub repository)

# 🌉 Bridge AI - AI-Powered Social Welfare Locator

> Connecting communities to essential services through intelligent location discovery and AI-powered assistance.

Built for the **UC Berkeley AI Hackathon 2025** 🎓

[![GitHub](https://img.shields.io/badge/GitHub-View%20Code-blue?logo=github)](https://github.com/minggangli1030/bridge-hackathon-2025)
[![Devpost](https://img.shields.io/badge/Devpost-Project%20Page-orange?logo=devpost)](https://devpost.com/software/bridge-h3pina)

## 🚀 Overview

Bridge AI is a comprehensive web application that helps individuals locate essential community services in their area. Using advanced AI technology and real-time data, it provides personalized recommendations for food banks, shelters, healthcare facilities, and other vital resources.

### ✨ Key Features

- **🔍 Intelligent Service Discovery**: Find food banks, shelters, healthcare, Wi-Fi hotspots, and more
- **🗺️ Interactive Mapping**: Visual location display with Google Maps integration
- **🤖 AI-Powered Chat Assistant**: Context-aware recommendations using Claude AI
- **📍 Real-Time Directions**: One-click navigation to any service location
- **🌡️ Weather-Aware Suggestions**: Recommendations adapt to current conditions
- **⏰ Time-Sensitive Filtering**: Shows currently open locations and operating hours

## 🛠️ Technology Stack

### Frontend
- **React 18** - Modern component-based UI
- **Tailwind CSS** - Utility-first styling
- **Google Maps API** - Interactive mapping and geocoding
- **@react-google-maps/api** - React Google Maps integration

### Backend
- **Node.js & Express** - RESTful API server
- **Google Places API** - Location data and business information
- **Anthropic Claude API** - AI-powered conversational assistance
- **OpenWeatherMap API** - Weather-based recommendations

### Key Libraries
- **CORS** - Cross-origin resource sharing
- **dotenv** - Environment variable management
- **node-fetch** - HTTP request handling

## 📦 Installation & Setup

### Prerequisites
- Node.js (v14 or higher)
- npm or yarn
- API keys for Google Maps, Google Places, Anthropic Claude, and OpenWeatherMap

### 1. Clone the Repository
```bash
git clone https://github.com/minggangli1030/bridge-hackathon-2025.git
cd bridge-hackathon-2025
```

### 2. Install Dependencies
```bash
# Install frontend dependencies
npm install

# Install backend dependencies
cd server
npm install
cd ..
```

### 3. Environment Configuration
Create a `.env` file in the `server/` directory:

```env
REACT_APP_GOOGLE_API_KEY=your_google_maps_api_key
REACT_APP_ANTHROPIC_API_KEY=your_anthropic_api_key
REACT_APP_WEATHER_API_KEY=your_openweathermap_api_key
```

### 4. API Key Setup

#### Google Maps & Places API
1. Visit [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select existing
3. Enable the following APIs:
   - Maps JavaScript API
   - Places API
   - Geocoding API
4. Create credentials and copy your API key

#### Anthropic Claude API
1. Sign up at [Anthropic Console](https://console.anthropic.com/)
2. Generate an API key
3. Add it to your environment variables

#### OpenWeatherMap API
1. Register at [OpenWeatherMap](https://openweathermap.org/api)
2. Get your free API key
3. Add it to your environment variables

### 5. Start the Application
```bash
# Terminal 1: Start the backend server
cd server
npm run dev

# Terminal 2: Start the React frontend
npm start
```

The application will be available at:
- **Frontend**: http://localhost:3000
- **Backend**: http://localhost:58080

## 🎯 How to Use

### 1. **Enter Location**
Input a 5-digit ZIP code for your search area

### 2. **Select Service Category**
Choose from available options:
- 🍽️ Food (food banks, soup kitchens, pantries)
- 💧 Water (drinking fountains, water stations)
- 📶 Free Wi-Fi (libraries, community centers, hotspots)
- 🏠 Shelters (emergency housing, temporary shelter)
- 🏥 Healthcare (community clinics, free medical care)
- 🚿 Showers (public facilities, community centers)
- 💼 Jobs (employment services, job centers)

### 3. **Search & Explore**
- View results in an organized list with ratings and distances
- See locations on an interactive map
- Click any location for details and directions

### 4. **Get AI Assistance**
- Ask the AI chatbot specific questions about services
- Receive personalized recommendations based on:
  - Current time and day of the week
  - Local weather conditions
  - Operating hours and availability
  - Your specific needs and circumstances

## 🏗️ Architecture

### Frontend Structure
```
src/
├── components/
│   ├── CategoryButtons.js    # Service category selection
│   ├── ZipInput.js          # Location input form
│   ├── ResourceList.js      # Service results display
│   ├── MapView.js           # Interactive Google Maps
│   └── Chatbot.js           # AI assistant interface
├── services/
│   ├── googlePlaces.js      # Places API integration
│   ├── anthropicService.js  # Claude AI communication
│   ├── contextService.js    # Context-aware recommendations
│   └── weatherService.js    # Weather data integration
└── App.js                   # Main application component
```

### Backend Structure
```
server/
├── index.js                 # Express server with API routes
├── package.json            # Backend dependencies
└── .env                    # Environment variables
```

### API Endpoints
- `GET /api/places?zip={ZIP}&category={CATEGORY}` - Fetch nearby services
- `POST /api/chat` - AI conversation proxy to Anthropic Claude

## 🔧 Key Features Deep Dive

### Smart Context Awareness
The AI assistant considers multiple factors when providing recommendations:
- **Time Context**: Adjusts suggestions based on current time and day
- **Weather Integration**: Prioritizes indoor locations during extreme weather
- **Operating Hours**: Highlights currently open services
- **Distance Calculation**: Uses Haversine formula for accurate distances
- **Urgency Assessment**: Escalates recommendations during critical conditions

### Advanced Location Filtering
Service categories are mapped to specific search queries:
```javascript
const queryMap = {
  food: 'food bank|soup kitchen|food pantry|free meals',
  water: 'drinking water|free water fountain|public water station',
  'free wi-fi': 'free wifi|public wi-fi|community wifi hotspot|library',
  // ... more categories
};
```

### Real-Time Data Integration
- Live weather data affects recommendations
- Operating hours determine availability
- Distance calculations help users find nearby options
- One-click directions to Google Maps for navigation

## 🎨 UI/UX Design

- **Clean, Accessible Interface**: Designed for users in potentially stressful situations
- **Mobile-Responsive**: Works on all device sizes
- **High Contrast**: Easy to read in various lighting conditions
- **Intuitive Navigation**: Minimal clicks to find essential information
- **Real-Time Feedback**: Loading states and error handling

## 🤖 AI Integration

Bridge AI leverages **Anthropic's Claude** to provide:
- Context-aware service recommendations
- Natural language understanding of user needs
- Weather and time-sensitive suggestions
- Personalized assistance based on location data
- Emergency situation awareness and appropriate responses

## 📊 Impact & Use Cases

### Primary Users
- **Individuals experiencing homelessness** seeking immediate resources
- **Social workers** helping clients find services
- **Community organizers** mapping available resources
- **Emergency responders** connecting people to assistance
- **Newcomers to an area** unfamiliar with local services

### Real-World Applications
- **Crisis Response**: Quick access to shelters during extreme weather
- **Daily Needs**: Finding food banks and community kitchens
- **Job Seeking**: Locating employment services and career centers
- **Healthcare Access**: Discovering free and low-cost medical clinics
- **Basic Amenities**: Finding public restrooms, Wi-Fi, and water sources

## 🚀 Future Enhancements

-

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 40 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (19 of 19)

```
.gitignore
package.json
public/index.html
README.md
server/index.js
server/package.json
src/App.js
src/components/CategoryButtons.js
src/components/Chatbot.js
src/components/MapView.js
src/components/ResourceList.js
src/components/ZipInput.js
src/index.css
src/index.js
src/services/anthropicService.js
src/services/contextService.js
src/services/googlePlaces.js
src/services/weatherService.js
tailwind.config.js
```

### Dependencies

- package.json: @react-google-maps/api@^2.20.7, autoprefixer@^10.4.21, cors@^2.8.5, node-fetch@^3.3.2, postcss@^8.5.6, react@^18.2.0, react-dom@^18.2.0, react-scripts@5.0.1, tailwindcss@^3.4.17
- server/package.json: cors@^2.8.5, dotenv@^16.3.1, express@^4.18.2, node-fetch@^3.3.2, nodemon@^3.0.1

### Recent commits (newest first)

- Update README.md
- Update README.md
- Fixsed chatbot prompting bug
- Added auto prompting for Claude
- Claude reponse improve
- Added location link pop up function
- Final submission backup
- Added live weather data to tailor Claude's response
- Screenshot backup
- Updated footnote
- Added footnote
- Finished project
- Interactive map and list
- Fixed size
- Backup working map and resource list
- Backup working resourcelist
- Added debug info
- Moved .env location
- Fixed port path in index.js
- Changed api key path in index.js

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "bridge-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@react-google-maps/api": "^2.20.7",
    "cors": "^2.8.5",
    "node-fetch": "^3.3.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build"
  },
  "proxy": "http://localhost:58080",
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "autoprefixer": "^10.4.21",
    "postcss": "^8.5.6",
    "tailwindcss": "^3.4.17"
  }
}

```

### server/package.json

```
{
  "name": "bridge-backend",
  "version": "1.0.0",
  "description": "Backend server for Bridge app",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "dotenv": "^16.3.1",
    "node-fetch": "^3.3.2",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}
```

### src/index.js

```javascript

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

```

### src/App.js

```javascript
import React, { useState, useEffect } from 'react';
import ZipInput from './components/ZipInput';
import CategoryButtons from './components/CategoryButtons';
import MapView from './components/MapView';
import ResourceList from './components/ResourceList';
import Chatbot from './components/Chatbot';

const App = () => {
  const [zip, setZip] = useState('');
  const [category, setCategory] = useState('');
  const [searchTrigger, setSearchTrigger] = useState(0);
  const [selectedPlaceId, setSelectedPlaceId] = useState(null);
  const [center, setCenter] = useState(null);
  const [places, setPlaces] = useState([]);

  // Triggered by clicking Search button
  const handleSearch = () => {
    if (!zip) {
      alert('Please enter a ZIP code');
      return;
    }
    if (!category) {
      alert('Please select a category');
      return;
    }
    setSearchTrigger(prev => prev + 1);
  };

  // Geocode for map center; does NOT affect ResourceList fetching
  useEffect(() => {
    if (searchTrigger === 0) return;
    (async () => {
      // Fetch resource list for map
      try {
        const placesRes = await fetch(
          `http://localhost:58080/api/places?zip=${zip}&category=${category}`
        );
        const placesData = await placesRes.json();
        setPlaces(placesData);
      } catch (err) {
        console.error('Error fetching places:', err);
        setPlaces([]);
      }

      try {
        const geoRes = await fetch(
          `https://maps.googleapis.com/maps/api/geocode/json?address=${zip}&key=${process.env.REACT_APP_GOOGLE_API_KEY}`
        );
        const geoData = await geoRes.json();
        const loc = geoData.results?.[0]?.geometry?.location;
        if (loc) setCenter({ lat: loc.lat, lng: loc.lng });
        else setCenter(null);
      } catch (err) {
        console.error('Error geocoding ZIP:', err);
        setCenter(null);
      }
    })();
  }, [searchTrigger, zip, category]);

  return (
    <div className="min-h-screen bg-gray-100 p-4">
      <h1 className="text-2xl font-bold text-center mb-4">
        🌉 Bridge AI - Find Community Services Instantly
      </h1>

      <ZipInput zip={zip} setZip={setZip} onSearch={handleSearch} />
      <CategoryButtons
        selectedCategory={category}
        setCategory={setCategory}
      />

      {zip && category && (
        <div className="text-center mb-4 text-gray-600">
          Searching for <strong>{category}</strong> near <strong>{zip}</strong>
        </div>
      )}

      <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
        {/* Left: resource list (handles its own fetch) */}
        <div className="bg-white rounded shadow overflow-y-auto h-[400px]">
          <ResourceList
            zip={zip}
            category={category}
            searchTrigger={searchTrigger}
            selectedPlaceId={selectedPlaceId}
            onSelect={setSelectedPlaceId}
          />
        </div>

        {/* Right: map view (only uses geocode results) */}
        <div className="bg-white rounded shadow h-[400px]">
          <MapView
            center={center}
            places={places}
            selectedPlaceId={selectedPlaceId}
            onSelect={setSelectedPlaceId}
          />
        </div>
        
        {/* Bottom half: Claude-powered chat */}
        <div className="h-[35vh] md:col-span-2 bg-white rounded shadow">
          <Chatbot zip={zip} category={category} places={places} />
        </div>

      </div>
      <footer className="mt-4 mb-4 text-center text-sm text-gray-500 bg-gray-50 py-2">
        Built for <a href="https://ai.hackberkeley.org/" className="text-blue-500 underline" target="_blank" rel="noopener noreferrer">UC Berkeley AI Hackathon</a> 2025.
        View on <a href="https://github.com/minggangli1030/bridge-hackathon-2025" className="text-blue-500 underline" target="_blank" rel="noopener noreferrer">GitHub</a> & 
        <a href="https://devpost.com/software/bridge-h3pina" className="text-blue-500 underline" target="_blank" rel="noopener noreferrer"> Devpost</a>. 
      </footer>
    </div>
  );
};

export default App;
```

### server/index.js

```javascript
require('dotenv').config();
console.log('🔑 Google key loaded:', process.env.REACT_APP_GOOGLE_API_KEY ? '✅' : '❌ missing');
console.log('🔑 Antropic key loaded:', process.env.REACT_APP_ANTHROPIC_API_KEY ? '✅' : '❌ missing');
const express = require('express');

// Node 18+: native fetch is available
const cors = require('cors'); // Add this dependency
const app = express();
const PORT = 58080;

// Convert degrees to radians
const toRad = deg => deg * Math.PI / 180;

// Haversine formula to compute distance between two coords in km
function haversineDistance(lat1, lng1, lat2, lng2) {
  const R = 6371; // Earth radius in km
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lng2 - lng1);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c; // returns distance in km
}

// Enable CORS for your React frontend
app.use(cors({
  origin: 'http://localhost:3000' // React dev server port
}));

app.use(express.json());

app.get('/api/places', async (req, res) => {
    try {
        const { zip, category } = req.query;
        const googleKey = process.env.REACT_APP_GOOGLE_API_KEY;

        if (!googleKey) {
            return res.status(500).json({ error: 'Google API key not configured' });
        }

        if (!zip || !category) {
            return res.status(400).json({ error: 'ZIP code and category are required' });
        }

        // Get coordinates from ZIP code
        const geoRes = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${zip}&key=${googleKey}`);
        const geoData = await geoRes.json();
        
        if (!geoData.results || geoData.results.length === 0) {
            return res.status(404).json({ error: 'Invalid ZIP code' });
        }

        const { lat, lng } = geoData.results[0].geometry.location;

        // Map categories to search queries
        const queryMap = {
            food: 'food bank|soup kitchen|food pantry|free meals',
            water: 'drinking water|free water fountain|public water station',
            'free wi-fi': 'free wifi|public wi-fi|community wifi hotspot|public library|community center',
            shelters: 'homeless shelter|emergency shelter|temporary housing',
            healthcare: 'community health clinic|free clinic|medical assistance',
            showers: 'public showers|community showers|shower facility|public restroom|community restroom|public bathroom',
            jobs: 'employment services|job center|career counseling|workforce development'
        };

        const query = encodeURIComponent(queryMap[category] || category);

        // Search for places
        const placeRes = await fetch(`https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&radius=5000&keyword=${query}&key=${googleKey}`);
        const places = await placeRes.json();

        if (places.status !== 'OK') {
            return res.status(500).json({ error: 'Google Places API error', details: places.status });
        }

        // Format the response
        const formattedPlaces = places.results.map(place => {
            // compute km, then miles, then format as string
            const distKm = haversineDistance(lat, lng, place.geometry.location.lat, place.geometry.location.lng);
            const distMiles = distKm * 0.621371;
            return {
                name: place.name,
                address: place.vicinity || place.formatted_address,
                // Ensure rating is always defined
                rating: place.rating !== undefined ? place.rating : 'No rating',
                place_id: place.place_id,
                geometry: place.geometry,
                opening_hours: place.opening_hours,
                photos: place.photos,
                // Format distance as string with one decimal place
                distance: `${distMiles.toFixed(1)} mi`
            };
        });

        res.json(formattedPlaces);
    } catch (error) {
        console.error('🔥 GooglePlaces Error:', error);
        return res.status(500).json({ error: error.message });
    }
});

// ✅ Anthropic proxy: forwards chat requests to Anthropic API
app.post('/api/chat', async (req, res) => {
  try {
    console.log('📥 Received chat request:', req.body);

    const apiKey = process.env.REACT_APP_ANTHROPIC_API_KEY;
    if (!apiKey) return res.status(500).json({ error: 'Missing API key' });

    const response = await fetch('https://api.anthropic.com/v1/messages', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': apiKey,
        'anthropic-version': '2023-06-01'
      },
      body: JSON.stringify({
        model: 'claude-3-opus-20240229',
        max_tokens: 1024,
        messages: req.body.messages
      }),
    });

    const data = await response.json(); // ✅ READ ONCE
    console.log('✅ Claude response:', data);
    res.json(data);

  } catch (err) {
    console.error('🔥 Anthropic proxy error:', err);
    res.status(500).json({ error: err.message });
  }
});

app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
    console.log(`Make sure your React app calls http://localhost:${PORT}/api/places`);
});
```

### tailwind.config.js

```javascript
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

### src/index.css

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### public/index.html

```html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Bridge AI</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

```

### src/components/CategoryButtons.js

```javascript
import React from 'react';

const categories = [
  { label: 'Food', value: 'food' },
  { label: 'Water', value: 'water' },
  { label: 'Free Wi-Fi', value: 'free wi-fi' },
  { label: 'Shelters', value: 'shelters' },
  { label: 'Healthcare', value: 'healthcare' },
  { label: 'Showers', value: 'showers' },
  { label: 'Jobs', value: 'jobs' }
];

const CategoryButtons = ({ setCategory, selectedCategory }) => {
  return (
    <div className="flex flex-wrap justify-center gap-2">
      {categories.map((cat) => (
        <button
          key={cat.value}
          onClick={() => setCategory(cat.value)}
          className={`px-3 py-2 rounded transition-colors ${
            selectedCategory === cat.value
              ? 'bg-green-600 text-white'
              : 'bg-green-500 text-white hover:bg-green-600'
          }`}
        >
          Nearby {cat.label}
        </button>
      ))}
    </div>
  );
};

export default CategoryButtons;
```

### src/components/ZipInput.js

```javascript
import React from 'react';

const ZipInput = ({ zip, setZip, onSearch }) => {
  const handleSubmit = (e) => {
    e.preventDefault();
    if (zip.length === 5 && /^\d{5}$/.test(zip)) {
      onSearch();
    } else {
      alert('Please enter a valid 5-digit ZIP code');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="flex justify-center mb-4">
      <input
        type="text"
        inputMode="numeric"
        pattern="[0-9]*"
        value={zip}
        onChange={e => setZip(e.target.value.replace(/\D/g, ''))}
        placeholder="Enter ZIP Code"
        className="border p-2 rounded-l-md w-40"
        maxLength={5}
      />
      <button
        type="submit"
        disabled={zip.length !== 5}
        className={`bg-blue-500 text-white px-4 py-2 rounded-r-md hover:bg-blue-600 ${
          zip.length !== 5 ? 'opacity-50 cursor-not-allowed' : ''
        }`}
      >
        Search
      </button>
    </form>
  );
};

export default ZipInput;
```

[7 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]