# Project export: Slug Loo

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: CruzHacks 2026
- Tagline: Slug Loo is a student-built map that helps UCSC students find bathrooms fast when they need them most. Know a bathroom location? Add it to the map and write an anonymous review for students to see!
- Devpost: https://devpost.com/software/slug-loo
- GitHub: https://github.com/AntPad27/Brathroom_app
- Video: https://www.youtube.com/embed/IS5T0IaAAhY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — annagiroti (10 commits), Anthony Padilla (5 commits)

## Devpost submission (written by the team)

### Inspiration

Our inspiration came from the idea of doing something fun and silly. We wanted to create something that could create an impact in our school, and as we talked, the topic of what bathrooms people should use compared to others came up, and we thought, why not rate them?

### What it does

Our project lets users add bathroom locations and leave reviews that other users can see. The added bathrooms can then receive multiple reviews from anonymous users, who can voice their complaints or praise a particular bathroom.

### How we built it

We built it using GitHub and other technologies listed below. We split up the work, made a list, checked off the boxes of what each of us got done, and added newer ones when new problems or ideas arose.

### Challenges we ran into

The big challenge we ran into was getting a project api keys for Google Maps. As we had to search through many documents and read the fine print, we dont want to spend any money on API calls. For the Google Maps API specifically, a billing method must have been added. Even if we had only used the free version, we closely monitored API calls to ensure we didn't exceed the free limit during testing.

### Accomplishments we're proud of

An accomplishment we are proud of is how far we have come. As we worked on this project, many new ideas kept popping into our heads, and as more and more problems arose, we were able to fix them and create a project we are proud of.

### What we learned

We learned how UX design can significantly affect users' interactions with the app. We also learned how to really collaborate on the same project with others: we didn't want to mess with each other's code, but we also needed to code on the same file.

### What's next

We came up with a lot of ideas on how we could continue to expand and work on this project to even maybe other UC campuses, or even have a full application version on users' phones.

## README (from the GitHub repository)

# UCSC Restroom Radar

**A web app for UCSC students to find, rate, and add bathrooms—with a focus on cleanliness, privacy, and accessibility—all anonymously.**

## 🚀 Quick Start (3 Steps)

### 1. Install Dependencies
```bash
npm install
```

### 2. Set Up Environment Variables
Copy `.env.local.example` to `.env.local` and fill in:
```bash
cp .env.local.example .env.local
```

You need:
- **Supabase URL & Key**: Create a free project at [supabase.com](https://supabase.com)
- **Google Maps API Key**: Get one at [Google Cloud Console](https://console.cloud.google.com)

### 3. Set Up Database
1. Go to your Supabase project → SQL Editor
2. Copy and paste everything from `supabase-schema.sql`
3. Click "Run" to create tables

### 4. Run the App
```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) 🎉

---

## 📂 Project Structure

```
Brathroom_app/
├── app/                          # Next.js App Router pages
│   ├── layout.js                 # Root layout (wraps all pages)
│   ├── globals.css               # Global styles with Tailwind
│   ├── page.js                   # Home page (map + bathroom list)
│   ├── add/page.js               # Add new bathroom
│   └── bathroom/[id]/
│       ├── page.js               # Bathroom detail page
│       └── review/page.js        # Add review form
├── components/
│   └── Map.js                    # Google Maps component
├── lib/
│   ├── supabase.js               # Supabase client setup
│   └── utils.js                  # Helper functions (device ID, distance calc)
├── supabase-schema.sql           # Database schema (run in Supabase)
├── .env.local.example            # Environment variables template
└── README.md
```

---

## 🗺️ Tech Stack

| Purpose | Technology |
|---------|-----------|
| Frontend Framework | **Next.js 16** (React with App Router) |
| Styling | **Tailwind CSS** |
| Database | **Supabase** (PostgreSQL) |
| Maps | **Google Maps JavaScript API** |
| Deployment | **Vercel** (recommended) |

---

## 📄 Pages & User Flows

### 1. Home Page (`/`)
- **Map** with bathroom pins
- **List** of bathrooms (sorted by distance if location enabled)
- **Search** by name/building
- **Filters** (wheelchair accessible, single stall, gender neutral)
- Click bathroom → go to detail page
- Click pin on map → go to detail page

### 2. Bathroom Detail (`/bathroom/[id]`)
- Name, building, features (tags)
- Average ratings (cleanliness, accessibility, privacy)
- All reviews with comments
- "Add Review" button

### 3. Add Review (`/bathroom/[id]/review`)
- 3 sliders (1-5) for cleanliness, accessibility, privacy
- Optional comment
- Anonymous (uses device hash)

### 4. Add Bathroom (`/add`)
- Name and building fields
- Click map to place pin (or uses your location)
- Accessibility checkboxes
- Submit → appears on map immediately

---

## 🗄️ Database Tables

### `bathrooms`
```sql
id                    UUID (primary key)
name                  TEXT
building              TEXT
lat                   FLOAT8
lng                   FLOAT8
wheelchair_accessible BOOLEAN
single_stall          BOOLEAN
gender_neutral        BOOLEAN
grab_bars             BOOLEAN
automatic_door        BOOLEAN
created_at            TIMESTAMP
```

### `reviews`
```sql
id            UUID (primary key)
bathroom_id   UUID (foreign key → bathrooms)
cleanliness   INTEGER (1-5)
accessibility INTEGER (1-5)
privacy       INTEGER (1-5)
comment       TEXT (optional)
device_hash   TEXT (for spam control)
created_at    TIMESTAMP
```

---

## 🔑 Key Features

✅ **Anonymous usage** - No login required  
✅ **Browse bathrooms** - Map + list view  
✅ **Filter & search** - By accessibility, building, etc.  
✅ **Distance sorting** - "Near me" using geolocation  
✅ **Rate bathrooms** - 3 categories (cleanliness, accessibility, privacy)  
✅ **Add bathrooms** - Click map to place pin  
✅ **Anti-spam** - Device-based hashing (can't spam reviews)  

---

## 🚢 Deployment

### Deploy to Vercel (Easiest)

1. Push your code to GitHub
2. Go to [vercel.com](https://vercel.com) → Import Project
3. Add environment variables:
   - `NEXT_PUBLIC_SUPABASE_URL`
   - `NEXT_PUBLIC_SUPABASE_ANON_KEY`
   - `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY`
4. Deploy! ✨

Your Supabase database stays hosted on Supabase automatically.

---

## 🛠️ How It Works

### Anonymous Anti-Spam
- On first visit, app generates a random `deviceId` (UUID)
- Stored in `localStorage` (stays on your browser)
- Before submitting review, deviceId is hashed using SHA-256
- Only the hash is stored in the database (privacy-friendly)
- Prevents multiple reviews from same device

### Distance Calculation
- Uses browser's `navigator.geolocation.getCurrentPosition()`
- Calculates distance with Haversine formula (client-side)
- No server-side geo queries needed for MVP

### Google Maps Integration
- `@googlemaps/react-wrapper` handles API loading
- Click map → place marker → get lat/lng
- Bathroom pins are clickable → navigate to detail page

---

## 📝 Environment Variables

Create `.env.local` in the root:

```bash
# Supabase (get from supabase.com → Project Settings → API)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here

# Google Maps (get from console.cloud.google.com)
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your-maps-api-key-here
```

---

## 🐛 Troubleshooting

### Map not showing?
- Check your Google Maps API key is correct
- Make sure you enabled "Maps JavaScript API" in Google Cloud Console
- Check browser console for errors

### Can't add bathroom/review?
- Check Supabase URL and anon key are correct
- Make sure you ran `supabase-schema.sql` in Supabase SQL Editor
- Check browser console for errors

### "Module not found" errors?
```bash
rm -rf node_modules package-lock.json
npm install
```

---

## 📦 What's Included

- ✅ All core pages (home, detail, review, add)
- ✅ Google Maps with clickable pins
- ✅ Supabase integration (database + queries)
- ✅ Anonymous reviews with spam protection
- ✅ Distance sorting
- ✅ Accessibility filters
- ✅ Mobile-responsive design
- ✅ Ready for Vercel deployment

---

## 🎯 MVP Scope

**Included:**
- Browse bathrooms (map + list)
- View details and ratings
- Add reviews anonymously
- Add new bathrooms
- Filter by accessibility

**Not included (can add later):**
- User accounts/login
- Photo uploads
- Hours of operation
- Reporting system
- Server-side geo queries

---

## 📚 Learn More

- [Next.js Documentation](https://nextjs.org/docs)
- [Supabase Documentation](https://supabase.com/docs)
- [Google Maps JavaScript API](https://developers.google.com/maps/documentation/javascript)
- [Tailwind CSS](https://tailwindcss.com/docs)

---

**Built with ❤️ for UCSC students**

## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 48 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code

## Codebase structure (from repository index)

### Files (19 of 19)

```
.gitignore
app/add/page.js
app/bathroom/[id]/page.js
app/bathroom/[id]/review/page.js
app/globals.css
app/layout.js
app/page.js
CODE-REFERENCE.md
components/Map.js
jsconfig.json
lib/supabase.js
lib/utils.js
next.config.js
package.json
postcss.config.js
README.md
SETUP.md
supabase-schema.sql
tailwind.config.js
```

### Dependencies

- package.json: @googlemaps/react-wrapper@^1.2.0, @supabase/supabase-js@^2.90.1, @tailwindcss/postcss@^4.1.18, autoprefixer@^10.4.23, next@^16.1.3, postcss@^8.5.6, react@^19.2.3, react-dom@^19.2.3, tailwindcss@^4.1.18

### Recent commits (newest first)

- updated main page title font
- fixed the bar height and the icon centering
- updated pin function for new rooms and changed star visuals
- added transparency transitions to buttons
- added the slugloo icon
- Merge branch 'main' of https://github.com/AntPad27/Brathroom_app
- updated the UI for page titles
- boundries
- new star rateings
- fixed review page params.id
- updated params.id components
- updated for reviews page
- suffnew
- stuff
- added read me

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

### SETUP.md

```markdown
# 🚀 SETUP CHECKLIST - Start Here!

Follow these steps IN ORDER to get your app running:

## ☐ Step 1: Install Dependencies
```bash
npm install
```
**Expected result:** Should complete without errors

---

## ☐ Step 2: Get Supabase Credentials

1. Go to https://supabase.com
2. Sign up / Log in
3. Click "New Project"
4. Fill in:
   - Name: `ucsc-bathroom-finder` (or anything)
   - Database Password: (generate or make one)
   - Region: Choose closest to you
5. Wait 1-2 minutes for project to be ready
6. Click "Project Settings" (gear icon) → API
7. Copy:
   - **Project URL** (looks like `https://abc123.supabase.co`)
   - **anon public** key (long string)

---

## ☐ Step 3: Get Google Maps API Key

1. Go to https://console.cloud.google.com
2. Create a new project (or select existing)
3. Go to "APIs & Services" → "Library"
4. Search for "Maps JavaScript API" → Enable it
5. Go to "APIs & Services" → "Credentials"
6. Click "Create Credentials" → "API Key"
7. Copy the API key
8. (Optional but recommended) Click "Restrict Key" → 
   - Set "Application restrictions" to "HTTP referrers"
   - Add `http://localhost:3000/*` for dev
   - Add your Vercel domain for production

---

## ☐ Step 4: Create .env.local File

1. Copy the example file:
   ```bash
   cp .env.local.example .env.local
   ```

2. Open `.env.local` and fill in your values:
   ```bash
   NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
   NEXT_PUBLIC_SUPABASE_ANON_KEY=your-long-anon-key-here
   NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your-google-maps-key-here
   ```

---

## ☐ Step 5: Create Database Tables

1. Go back to Supabase → your project
2. Click "SQL Editor" in the left sidebar
3. Open the file `supabase-schema.sql` in this project
4. **Copy ALL the SQL code** (Cmd+A or Ctrl+A)
5. **Paste it** into the Supabase SQL Editor
6. Click "Run" button (or Cmd+Enter)
7. You should see "Success. No rows returned" ✓

To verify:
- Click "Table Editor" in Supabase
- You should see `bathrooms` and `reviews` tables

---

## ☐ Step 6: Run the Development Server

```bash
npm run dev
```

**Expected result:**
```
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
```

Open http://localhost:3000 in your browser!

---

## ✅ Testing Your App

### Test 1: Add a Bathroom
1. Click "Add Bathroom" button
2. Fill in name (e.g., "McHenry Library 1st Floor")
3. Fill in building (e.g., "McHenry Library")
4. Click anywhere on the map to place a pin
5. Check accessibility boxes
6. Click "Add Bathroom"
7. You should be redirected to home page
8. Your bathroom should appear on the map!

### Test 2: Add a Review
1. Click on your bathroom (on map or in list)
2. Click "Add Review"
3. Slide the ratings
4. Type a comment (optional)
5. Click "Submit Review"
6. You should see your review on the bathroom detail page!

### Test 3: Search & Filter
1. Go back to home page
2. Type in the search box
3. Toggle the accessibility filters
4. List should update in real-time

---

## 🐛 Common Issues

### "Module not f
[truncated — 1232 more characters]
```

### CODE-REFERENCE.md

```markdown
# 📖 CODE REFERENCE - Understanding the Codebase

Quick guide to where everything is and what it does.

---

## 🗂️ File Structure

```
Brathroom_app/
│
├── 📄 Configuration Files
│   ├── package.json           # Dependencies and scripts
│   ├── next.config.js         # Next.js config
│   ├── tailwind.config.js     # Tailwind CSS config
│   ├── postcss.config.js      # PostCSS (needed for Tailwind)
│   ├── .env.local.example     # Template for environment variables
│   └── .gitignore             # Files to ignore in git
│
├── 📚 Documentation
│   ├── README.md              # Main documentation (you are here)
│   ├── SETUP.md               # Step-by-step setup guide
│   ├── CODE-REFERENCE.md      # This file
│   └── supabase-schema.sql    # Database schema (run in Supabase)
│
├── 🎨 Frontend (app/)
│   ├── layout.js              # Root layout for all pages
│   ├── globals.css            # Global CSS (Tailwind imports)
│   ├── page.js                # Home page (/)
│   ├── add/page.js            # Add bathroom page (/add)
│   └── bathroom/[id]/
│       ├── page.js            # Bathroom detail (/bathroom/123)
│       └── review/page.js     # Add review (/bathroom/123/review)
│
├── 🧩 Components (components/)
│   └── Map.js                 # Google Maps component
│
└── 🛠️ Utilities (lib/)
    ├── supabase.js            # Supabase client
    └── utils.js               # Helper functions
```

---

## 📄 Key Files Explained

### `app/page.js` - Home Page
**What it does:**
- Shows map with bathroom pins
- Lists all bathrooms
- Search and filter functionality
- Sorts by distance if location enabled

**Key parts:**
```javascript
fetchBathrooms()           // Gets all bathrooms from Supabase
filteredBathrooms          // Filters by search + accessibility
calculateDistance()        // Sorts by distance from user
```

---

### `app/bathroom/[id]/page.js` - Bathroom Detail
**What it does:**
- Shows bathroom info and features
- Displays average ratings
- Lists all reviews

**Key parts:**
```javascript
fetchBathroom()            // Gets single bathroom by ID
fetchReviews()             // Gets all reviews for bathroom
averages                   // Calculates avg ratings client-side
```

---

### `app/bathroom/[id]/review/page.js` - Add Review
**What it does:**
- Form with 3 sliders (cleanliness, accessibility, privacy)
- Optional comment
- Submits anonymous review

**Key parts:**
```javascript
getDeviceId()              // Gets/creates device ID from localStorage
hashDeviceId()             // Hashes device ID for privacy
supabase.insert()          // Saves review to database
```

---

### `app/add/page.js` - Add Bathroom
**What it does:**
- Form to add new bathroom
- Click map to place pin
- Accessibility checkboxes

**Key parts:**
```javascript
AddBathroomMap             // Map component with click handler
handleLocationSelect()     // Captures lat/lng from map click
supabase.insert()          // Saves bathroom to database
```

---

### `components/Map.js` - Go
[truncated — 5492 more characters]
```

### package.json

```
{
  "name": "brathroom-app",
  "version": "1.0.0",
  "description": "UCSC Bathroom Finder - Find, rate, and add bathrooms on campus",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@googlemaps/react-wrapper": "^1.2.0",
    "@supabase/supabase-js": "^2.90.1",
    "@tailwindcss/postcss": "^4.1.18",
    "autoprefixer": "^10.4.23",
    "next": "^16.1.3",
    "postcss": "^8.5.6",
    "react": "^19.2.3",
    "react-dom": "^19.2.3",
    "tailwindcss": "^4.1.18"
  }
}

```

### app/layout.js

```javascript
import { Roboto } from 'next/font/google'
import './globals.css'

const roboto = Roboto({
  weight: ['400', '700'],
  subsets: ['latin'],
})

export const metadata = {
  title: 'UCSC Restroom Radar',
  description: 'Find, rate, and add bathrooms on campus',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={roboto.className}>
      <body>{children}</body>
    </html>
  )
}

```

### app/page.js

```javascript
'use client'

import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'
import { calculateDistance } from '@/lib/utils'
import Link from 'next/link'
import Map from '@/components/Map'

export default function Home() {
  const [bathrooms, setBathrooms] = useState([])
  const [userLocation, setUserLocation] = useState(null)
  const [filter, setFilter] = useState({ wheelchair: false, singleStall: false, genderNeutral: false })
  const [search, setSearch] = useState('')

  useEffect(() => {
    // Get user location
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        (position) => {
          setUserLocation({
            lat: position.coords.latitude,
            lng: position.coords.longitude
          })
        },
        (error) => console.log('Location denied:', error)
      )
    }

    // Fetch bathrooms
    fetchBathrooms()
  }, [])

  async function fetchBathrooms() {
    const { data, error } = await supabase
      .from('bathrooms')
      .select('*')
    
    if (error) {
      console.error('Error fetching bathrooms:', error)
    } else {
      setBathrooms(data)
    }
  }

  // Filter and sort bathrooms
  const filteredBathrooms = bathrooms
    .filter(b => {
      const matchesSearch = b.name.toLowerCase().includes(search.toLowerCase()) ||
                           b.building.toLowerCase().includes(search.toLowerCase())
      const matchesFilter = 
        (!filter.wheelchair || b.wheelchair_accessible) &&
        (!filter.singleStall || b.single_stall) &&
        (!filter.genderNeutral || b.gender_neutral)
      return matchesSearch && matchesFilter
    })
    .sort((a, b) => {
      if (!userLocation) return 0
      const distA = calculateDistance(userLocation.lat, userLocation.lng, a.lat, a.lng)
      const distB = calculateDistance(userLocation.lat, userLocation.lng, b.lat, b.lng)
      return distA - distB
    })

  return (
    <div className="min-h-screen" >
      <header className="p-4 w-full" style={{ backgroundColor: '#003c6c', color: '#fdc700', minHeight: '120px'  }}>
        <div className="flex items-start gap-3">
          <img src="/slugloo-icon.PNG" alt="Slug Loo Icon" className="w-16 h-16" />
          <div>
            <h1 className="text-4xl font-bold">Slug Loo</h1>
            <p className="text-sm">Find, rate, and add bathrooms on campus</p>
          </div>
        </div>
      </header>

      <main className="container mx-auto p-4">
        {/* Search and Filters */}
        <div className="bg-white p-4 rounded-lg shadow mb-4">
          <input
            type="text"
            placeholder="Search by name or building..."
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            className="w-full p-2 border rounded mb-3"
            style={{ border: '1px solid #003c6c' }}
          />
          
          <div className="flex gap-3 flex-wrap">
            <label className="flex items-center gap-2">
              <input
                type="checkbox"
                checked={filter.wheelchair}
                onChange={(e) => setFilter({...filter, wheelchair: e.target.checked})}
              />
              Wheelchair Accessible
            </label>
            <label className="flex items-center gap-2">
              <input
                type="checkbox"
                checked={filter.singleStall}
                onChange={(e) => setFilter({...filter, singleStall: e.target.checked})}
              />
              Single Stall
            </label>
            <label className="flex items-center gap-2">
              <input
                type="checkbox"
                checked={filter.genderNeutral}
                onChange={(e) => setFilter({...filter, genderNeutral: e.target.checked})}
              />
              Gender Neutral
            </label>
          </div>
        </div>

        {/* Map */}
        <div className="mb-4">
          <Map bathrooms={filteredBathrooms} userLocation={userLocation} />
        </div>

        {/* Add Bathroom Button */}
        <div className="mb-4">
          <Link 
            href="/add"
            className="inline-block px-6 py-3 rounded-lg font-semibold hover:opacity-80 transition"
            style={{ backgroundColor: '#003c6c', color: '#fff' }}
          >
            + Add Bathroom
          </Link>
        </div>

        {/* Bathroom List */}
        <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
          {filteredBathrooms.map((bathroom) => (
            <Link 
              key={bathroom.id}
              href={`/bathroom/${bathroom.id}`}
              className="bg-white p-4 rounded-lg shadow hover:shadow-lg transition"
            >
              <h3 className="font-bold text-lg">{bathroom.name}</h3>
              <p className="text-gray-600">{bathroom.building}</p>
              
              <div className="flex gap-2 mt-2 flex-wrap">
                {bathroom.wheelchair_accessible && (
                  <span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">♿ Accessible</span>
                )}
                {bathroom.single_stall && (
                  <span className="bg-purple-100 text-purple-800 text-xs px-2 py-1 rounded">Single Stall</span>
                )}
                {bathroom.gender_neutral && (
                  <span className="bg-green-100 text-green-800 text-xs px-2 py-1 rounded">Gender Neutral</span>
                )}
              </div>
              
              {userLocation && (
                <p className="text-sm text-gray-500 mt-2">
                  {calculateDistance(userLocation.lat, userLocation.lng, bathroom.lat, bathroom.lng).toFixed(2)} km away
                </p>
              )}
            </Link>
          ))}
        </div>

        {filteredBathrooms.length === 0 && (
          <p className="text-center text-gray-500 py-8">No bathrooms found. Be the first to add one!</p>
        )}
      </main>
    </div>
  )
[truncated — 3 more characters]
```

### app/add/page.js

```javascript
'use client'

import { useState, useEffect, useRef } from 'react'
import { useRouter } from 'next/navigation'
import { supabase } from '@/lib/supabase'
import { Wrapper } from '@googlemaps/react-wrapper'
import Link from 'next/link'

// UCSC campus bounds
const UCSC_BOUNDS = {
  north: 37.0050,
  south: 36.9750,
  west: -122.0750,
  east: -122.0450,
}

function AddBathroomMap({ onLocationSelect, initialLocation }) {
  const mapRef = useRef(null)
  const [map, setMap] = useState(null)
  const markerRef = useRef(null)

  useEffect(() => {
    if (!mapRef.current) return

    const newMap = new window.google.maps.Map(mapRef.current, {
      center: initialLocation || { lat: 36.9914, lng: -122.0609 },
      zoom: 16,
      restriction: {
        latLngBounds: UCSC_BOUNDS,
        strictBounds: false,
      },
    })

    setMap(newMap)

    // Add click listener to place marker
    newMap.addListener('click', (e) => {
      const lat = e.latLng.lat()
      const lng = e.latLng.lng()
      
      // Remove old marker
      if (markerRef.current) markerRef.current.setMap(null)
      
      // Add new marker
      const newMarker = new window.google.maps.Marker({
        position: { lat, lng },
        map: newMap,
      })
      
      markerRef.current = newMarker  // Store in ref
      onLocationSelect(lat, lng)
    })
  }, [])

  return <div ref={mapRef} style={{ width: '100%', height: '400px' }} />
}

export default function AddBathroom() {
  const router = useRouter()
  const [name, setName] = useState('')
  const [building, setBuilding] = useState('')
  const [lat, setLat] = useState(null)
  const [lng, setLng] = useState(null)
  const [userLocation, setUserLocation] = useState(null)
  const [wheelchair, setWheelchair] = useState(false)
  const [singleStall, setSingleStall] = useState(false)
  const [genderNeutral, setGenderNeutral] = useState(false)
  const [grabBars, setGrabBars] = useState(false)
  const [automaticDoor, setAutomaticDoor] = useState(false)
  const [submitting, setSubmitting] = useState(false)

  useEffect(() => {
    // Get user location
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        (position) => {
          const location = {
            lat: position.coords.latitude,
            lng: position.coords.longitude
          }
          setUserLocation(location)
          setLat(location.lat)
          setLng(location.lng)
        }
      )
    }
  }, [])

  function handleLocationSelect(latitude, longitude) {
    setLat(latitude)
    setLng(longitude)
  }

  async function handleSubmit(e) {
    e.preventDefault()
    
    if (!lat || !lng) {
      alert('Please select a location on the map')
      return
    }
    
    setSubmitting(true)

    const { error } = await supabase
      .from('bathrooms')
      .insert({
        name,
        building,
        lat,
        lng,
        wheelchair_accessible: wheelchair,
        single_stall: singleStall,
        gender_neutral: genderNeutral,
        grab_bars: grabBars,
        automatic_door: automaticDoor
      })

    if (error) {
      console.error('Error adding bathroom:', error)
      alert('Error adding bathroom')
    } else {
      router.push('/')
    }
    
    setSubmitting(false)
  }

  return (
    <div className="min-h-screen">
      <header className="p-4 w-full" style={{ backgroundColor: '#003c6c', color: '#fdc700', minHeight: '120px'  }}>
        <div className="flex items-start gap-3">
          <img src="/slugloo-icon.PNG" alt="Slug Loo Icon" className="w-16 h-16" />
          <div>
            <Link href="/" className="text-sm hover:underline">← Back to Map</Link>
            <h1 className="text-2xl font-bold mt-2">Add Bathroom</h1>
          </div>
        </div>
      </header>

      <main className="container mx-auto p-4 max-w-3xl">
        <form onSubmit={handleSubmit} className="space-y-4">
          
          {/* Basic Info */}
          <div className="bg-white p-6 rounded-lg shadow">
            <h2 className="font-bold mb-4">Basic Information</h2>
            
            <div className="mb-4">
              <label className="block font-medium mb-2">Name *</label>
              <input
                type="text"
                value={name}
                onChange={(e) => setName(e.target.value)}
                required
                className="w-full p-2 border rounded"
                placeholder="e.g., McHenry Library 1st Floor"
                style={{ border: '1px solid #003c6c' }}
              />
            </div>

            <div className="mb-4">
              <label className="block font-medium mb-2">Building *</label>
              <input
                type="text"
                value={building}
                onChange={(e) => setBuilding(e.target.value)}
                required
                className="w-full p-2 border rounded"
                placeholder="e.g., McHenry Library"
                style={{ border: '1px solid #003c6c' }}
              />
            </div>
          </div>

          {/* Map */}
          <div className="bg-white p-6 rounded-lg shadow">
            <h2 className="font-bold mb-2">Location *</h2>
            <p className="text-sm text-gray-600 mb-3">Click on the map to place a pin</p>
            
            <Wrapper apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}>
              <AddBathroomMap 
                onLocationSelect={handleLocationSelect}
                initialLocation={userLocation}
              />
            </Wrapper>
            
            {lat && lng && (
              <p className="text-sm text-green-600 mt-2">
                ✓ Location selected: {lat.toFixed(5)}, {lng.toFixed(5)}
              </p>
            )}
          </div>

          {/* Accessibility */}
          <div className="bg-white p-6 rounded-lg shadow">
            <h2 className="font-bold mb-4">Accessibility Features</h2>
            
            <div className="space-y-2">
              <label classN
[truncated — 1987 more characters]
```

### app/bathroom/[id]/page.js

```javascript
'use client'

import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'
import Link from 'next/link'

export default function BathroomDetail({ params }) {
  const [bathroom, setBathroom] = useState(null)
  const [reviews, setReviews] = useState([])
  const [averages, setAverages] = useState({ cleanliness: 0, accessibility: 0, privacy: 0 })

  useEffect(() => {
    fetchBathroom()
    fetchReviews()
  }, [])

  async function fetchBathroom() {
    const { id } = await params
    const { data, error } = await supabase
      .from('bathrooms')
      .select('*')
      .eq('id', id)
      .single()
    
    if (error) {
      console.error('Error fetching bathroom:', error)
    } else {
      setBathroom(data)
    }
  }

  async function fetchReviews() {
    const { id } = await params
    const { data, error } = await supabase
      .from('reviews')
      .select('*')
      .eq('bathroom_id', id)
      .order('created_at', { ascending: false })
    
    if (error) {
      console.error('Error fetching reviews:', error)
    } else {
      setReviews(data)
      
      // Calculate averages
      if (data.length > 0) {
        const avgCleanliness = data.reduce((sum, r) => sum + r.cleanliness, 0) / data.length
        const avgAccessibility = data.reduce((sum, r) => sum + r.accessibility, 0) / data.length
        const avgPrivacy = data.reduce((sum, r) => sum + r.privacy, 0) / data.length
        
        setAverages({
          cleanliness: avgCleanliness.toFixed(1),
          accessibility: avgAccessibility.toFixed(1),
          privacy: avgPrivacy.toFixed(1)
        })
      }
    }
  }

  if (!bathroom) return <div className="p-8">Loading...</div>

  return (
    <div className="min-h-screen">
      <header className="p-4 w-full" style={{ backgroundColor: '#003c6c', color: '#fdc700', minHeight: '120px'  }}>
        <div className="flex items-start gap-3">
          <img src="/slugloo-icon.PNG" alt="Slug Loo Icon" className="w-16 h-16" />
          <div>
            <Link href="/" className="text-sm hover:underline">← Back to Map</Link>
            <h1 className="text-2xl font-bold mt-2">{bathroom.name}</h1>
            <p className="text-sm">{bathroom.building}</p>
          </div>
        </div>
      </header>

      <main className="container mx-auto p-4 max-w-3xl">
        {/* Tags */}
        <div className="bg-white p-4 rounded-lg shadow mb-4">
          <h2 className="font-bold mb-2">Features</h2>
          <div className="flex gap-2 flex-wrap">
            {bathroom.wheelchair_accessible && (
              <span className="bg-blue-100 text-blue-800 px-3 py-1 rounded">♿ Wheelchair Accessible</span>
            )}
            {bathroom.single_stall && (
              <span className="bg-purple-100 text-purple-800 px-3 py-1 rounded">Single Stall</span>
            )}
            {bathroom.gender_neutral && (
              <span className="bg-green-100 text-green-800 px-3 py-1 rounded">Gender Neutral</span>
            )}
            {bathroom.grab_bars && (
              <span className="bg-gray-100 text-gray-800 px-3 py-1 rounded">Grab Bars</span>
            )}
            {bathroom.automatic_door && (
              <span className="bg-gray-100 text-gray-800 px-3 py-1 rounded">Automatic Door</span>
            )}
          </div>
        </div>

        {/* Ratings */}
        <div className="bg-white p-4 rounded-lg shadow mb-4">
          <h2 className="font-bold mb-3">Average Ratings</h2>
          <div className="space-y-2">
            <div>
              <span className="font-medium">Cleanliness:</span> {averages.cleanliness || 'No ratings yet'} / 5
            </div>
            <div>
              <span className="font-medium">Accessibility:</span> {averages.accessibility || 'No ratings yet'} / 5
            </div>
            <div>
              <span className="font-medium">Privacy:</span> {averages.privacy || 'No ratings yet'} / 5
            </div>
          </div>
        </div>

        {/* Add Review Button */}
        <div className="mb-4">
          <Link 
            href={`/bathroom/${bathroom.id}/review`}
            className="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 hover:opacity-80 transition"
            style={{ backgroundColor: '#003c6c', color: '#fff' }}
          >
            Add Review
          </Link>
        </div>

        {/* Reviews */}
        <div className="bg-white p-4 rounded-lg shadow">
          <h2 className="font-bold mb-3">Reviews ({reviews.length})</h2>
          <div className="space-y-4">
            {reviews.map((review) => (
              <div key={review.id} className="border-b pb-4 last:border-0">
                <div className="flex gap-4 text-sm text-gray-600 mb-2">
                  <span>Cleanliness: {review.cleanliness}/5</span>
                  <span>Accessibility: {review.accessibility}/5</span>
                  <span>Privacy: {review.privacy}/5</span>
                </div>
                {review.comment && (
                  <p className="text-gray-800">{review.comment}</p>
                )}
                <p className="text-xs text-gray-400 mt-2">
                  {new Date(review.created_at).toLocaleDateString()}
                </p>
              </div>
            ))}
            
            {reviews.length === 0 && (
              <p className="text-gray-500">No reviews yet. Be the first!</p>
            )}
          </div>
        </div>
      </main>
    </div>
  )
}

```

### app/bathroom/[id]/review/page.js

```javascript
'use client'

import { use, useState } from 'react'
import { useRouter } from 'next/navigation'
import { supabase } from '@/lib/supabase'
import { getDeviceId, hashDeviceId } from '@/lib/utils'
import Link from 'next/link'

export default function AddReview({ params }) {
  const { id } = use(params)
  const router = useRouter()
  const [cleanliness, setCleanliness] = useState(3)
  const [accessibility, setAccessibility] = useState(3)
  const [privacy, setPrivacy] = useState(3)
  const [comment, setComment] = useState('')
  const [submitting, setSubmitting] = useState(false)

  async function handleSubmit(e) {
    e.preventDefault()
    setSubmitting(true)

    const deviceId = getDeviceId()
    const deviceHash = await hashDeviceId(deviceId)

    const { error } = await supabase
      .from('reviews')
      .insert({
        bathroom_id: id,
        cleanliness,
        accessibility,
        privacy,
        comment: comment.trim() || null,
        device_hash: deviceHash
      })

    if (error) {
      console.error('Error submitting review:', error)
      alert('Error submitting review. You may have already reviewed this bathroom recently.')
    } else {
      router.push(`/bathroom/${id}`)
    }
    
    setSubmitting(false)
  }

  const StarRating = ({ value, onChange, label }) => {
    return (
      <div className="mb-6">
        <label className="block font-bold mb-3">{label}</label>
        <div className="flex gap-2">
          {[1, 2, 3, 4, 5].map((star) => (
            <button
              key={star}
              type="button"
              onClick={() => onChange(star)}
              className="text-4xl transition-all hover:scale-110"
            >
            <span style={{ color: star <= value ? '#fdc700' : '#003c6c' }}>
              {star <= value ? '★' : '★'}
            </span>
            </button>
          ))}
        </div>
      </div>
    )
  }

  return (
    <div className="min-h-screen">
      <header className="p-4 w-full" style={{ backgroundColor: '#003c6c', color: '#fdc700', minHeight: '120px'  }}>
        <div className="flex items-start gap-3">
          <img src="/slugloo-icon.PNG" alt="Slug Loo Icon" className="w-16 h-16" />
          <div>
            <Link href={`/bathroom/${id}`} className="text-sm hover:underline">← Back</Link>
            <h1 className="text-2xl font-bold mt-2">Add Review</h1>
          </div>
        </div>
      </header>

      <main className="container mx-auto p-4 max-w-2xl">
        <form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow">
          
          {/* Cleanliness */}
          <StarRating 
            value={cleanliness}
            onChange={setCleanliness}
            label="Cleanliness"
          />

          {/* Accessibility */}
          <StarRating 
            value={accessibility}
            onChange={setAccessibility}
            label="Accessibility"
          />

          {/* Privacy */}
          <StarRating 
            value={privacy}
            onChange={setPrivacy}
            label="Privacy"
          />

          {/* Comment */}
          <div className="mb-6">
            <label className="block font-bold mb-2">
              Comment (optional)
            </label>
            <textarea
              value={comment}
              onChange={(e) => setComment(e.target.value)}
              rows="4"
              className="w-full p-2 border rounded"
              placeholder="Share your experience..."
              style={{ border: '1px solid #003c6c' }}
            />
          </div>

          <button
            type="submit"
            disabled={submitting}
            className="w-full py-3 rounded-lg font-semibold hover:bg-blue-700 disabled:bg-gray-400 hover:opacity-80 transition"
            style={{ backgroundColor: submitting ? undefined : '#003c6c', color: '#fff' }}
          >
            {submitting ? 'Submitting...' : 'Submit Review'}
          </button>
        </form>
      </main>
    </div>
  )
}

```

### postcss.config.js

```javascript
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

```

### next.config.js

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
}

module.exports = nextConfig

```

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