# Project export: Homi

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: Cal Hacks 12.0
- Tagline: Let AI build the listing and you enjoy the stay.
- Devpost: https://devpost.com/software/homi-5mxbnw
- GitHub: https://github.com/thenerd31/homi
- Video: https://www.youtube.com/embed/L3kQw5A5pi0?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Aswin Surya (42 commits), Yuxin Zeng (23 commits), weetacode (6 commits), Claude (1 commits)

## Devpost submission (written by the team)

### Inspiration

A big part of our inspiration was taken from AirBnb and the features and issues that the app had solved. Yet, when re-imagining it as if it were built in 2025 within the big age of AI, it felt intuitive to have incorporated AI into streamlining the grueling process of real estate. From a consumer side, it's often difficult to parse through thousands of different listings just to be able to find someplace safe, affordable, and available for you to stay at during a vacation. From a seller's side, the process ranging from having to manually obtain sample photos of your property to deciding how much to charge when you have zero prior experience in selling a property, is both challenging and difficult.

### What it does

Homi makes it easy for guests to find safe, affordable stays and helps hosts showcase their properties to maximize profit through the following features... AI agents curating personalized, potential homes for consumers to stay at AI agents automating the listing process, removing any headache from a first time seller.

### How we built it

Homi is built entirely on a FastAPI backend and Next.js frontend. The backend itself runs on uvicorn with REST API + Websocket endpoints. Core Technical Implementations For usage of real-time object detection we employ the use of YOLOv8n. An open-source model coming from a large family of similar object detection usage. This model runs server-side with session-based aggregation storing detections in-memory with frame metadata. We also use this to capture and later transmit images as data URIs over WSS. We employ Groq llama 3.3 70b to extract structured search params (location, dates, guests, price) from natural language. A state machine then tracks missing params, and triggers follow-up questions through the use of Anthropic's Claude Sonnet 4.5. Once complete, params trigger Elasticsearch hybrid search with relevance threshold filtering. Another key feature is our Tinder-style swiping interface. Using Framer Motion gesture handlers on React client. each swipe tracked to Supabase + Letta for preference learning. Saved listings auto-ranked using Claude with contextual reasoning (price drops, preference alignment, availability, etc.)

### Challenges we ran into

AirBnb itself doesn't offer open API keys to students and therefore, we had to be clever in the ways in which we obtained the listing data. We had to pivot a couple of times. Beginning from an AR-powered application, we were unfortunate in our attempts to try and automate machine learning workflows on-top of our hardware and obtain outputs in a server-less environment. We then centered ourselves around an AI-native approach through the use of open-source models and AI agents.

### Accomplishments we're proud of

Creating a fully, custom designed, suite. One that incorporates elements from every single one of us meaning that this project is entirely our own. Arriving to a finished product, as a deliverable isn't always promised despite that being the entire point of Hackathons. Doing this with two first-time Hackathon attenders nonetheless!

### What we learned

Lots, and lots about hardware. Hardware, in the form of AR glasses, served as one of the main axioms and previous "invariants" to our product as it enabled custom pricing for listings as well as customer previews; having a hand in both buyer + seller workflows. Also, since one of our teammates had a background as a designer we really could delegate a lot more time into making a User-focused design + maintain a Product-focused mindset. These two paired together resulted in a beautiful UI made with users in mind to appease their life and make using our application all that much easier.

### What's next

Globalization! Currently Homi is only available for US locations. A future plan that we've all thought about is how we can globalize Homi for any person hoping to find a nice place to stay without all of that headache associated.

## README (from the GitHub repository)

# Homi - Conversational Vacation Rental Search

## The Problem

Over 80% of Airbnb hosts are individuals, not companies. Many list only one property—a family beach house, a mountain cabin they inherited, a spare room in their home. These everyday people lack the time or design skills to create professional listings. They're stuck filling endless forms, writing descriptions that sound generic, guessing at competitive prices, and hoping their amateur iPhone photos attract bookings.

On the flip side, guests scroll endlessly through hundreds of listings, clicking through 20+ photos per property, trying to feel a vibe from static images. They toggle through 50+ filter dropdowns, read descriptions that all say "cozy" and "charming," and still can't tell if a place actually matches what they're looking for.

The process is slow, unpersonalized, and fundamentally outdated.

**What if listing and finding vacation rentals didn't have to take hours?**

## What We Built

Homi reimagines vacation rental search as a conversation, not a form. Instead of toggling filters, you describe what you want. Instead of scrolling endlessly, you swipe through intelligently matched properties. Instead of guessing at pricing and amenities, AI handles it automatically.

We built four key innovations that make this possible:

**Prompt-Based Semantic Search**
Say goodbye to keyword matching. We trained our search to understand what you *mean*, not just what you say. Ask for "a cozy place with beach vibes" and it finds properties with ocean views and hammocks, even if the listing never uses those exact words. The system uses vector embeddings to understand intent—it's like having a friend who actually gets what you're looking for instead of a robot that only knows exact matches.

**Smart Mapping with Dynamic Radius**
Ever search for "San Francisco" and get results in San Jose 50 miles away? We built a geocoding system that understands city sizes. Search for NYC and it looks within 35 miles (because NYC is huge). Search for Napa and it keeps it tight at 15 miles (because that's the whole valley). The system automatically maps over 40 US cities to GPS coordinates and adjusts search radius based on how sprawling or compact each city actually is.

**AI-Powered Dynamic Pricing**
Hosts don't have to guess what to charge anymore. Our pricing engine analyzes market rates for similar properties, calculates demand patterns (weekends vs weekdays, holiday premiums), and suggests competitive pricing with actual reasoning: "Similar 3-bedroom beach houses in your area go for $350-$450. Your ocean view and hot tub justify $380 on weekends. Holiday season? Bump it to $450."

**Real-Time Object Detection for Listings**
When hosts create listings, our vision system scans uploaded photos to automatically detect amenities. Upload a photo of your backyard and it spots the pool, hot tub, and BBQ grill. Show it your kitchen and it recognizes modern appliances and granite countertops. The system identifies over 50 property features automatically—no more manually checking boxes for every amenity. It also scores image quality, filtering out blurry shots and keeping only the best angles.

## What Makes It Different

**Traditional vacation rental search:**
- Toggle through 50+ filter dropdowns
- Scroll through endless listings
- Read generic descriptions
- No learning or personalization

**Homi:**
- Describe what you want in plain English (or by voice)
- Swipe through intelligently matched results
- System learns your preferences
- Auto-organized saved listings

---

## How It Works

### 1. Conversational Search

Instead of filter menus, just describe what you want:

```
You: "Beach house in California"
Homi: "When are you planning to visit?"
You: "Next weekend, 3 people"
Homi: "What's your budget per night?"
You: "Under $300"
```

The system extracts structured parameters (location, dates, guests, budget) from natural language across multiple conversation turns.

### 2. Semantic Search & Matching

Uses vector embeddings to understand intent beyond keywords:
- "Beach house with hot tub" matches properties even with different wording
- Geographic radius filtering (dynamic based on city size)
- Relevance threshold filtering (only show quality matches)
- Hybrid approach: semantic + keyword + structured filters

### 3. Swipe Interface

Results displayed in Tinder-inspired cards showing:
- Photo carousel
- Amenities
- Location + distance
- Price per night
- Beds/baths
- Rating

Swipe right = save, left = pass. System learns from swipe patterns.

### 4. Preference Learning

Tracks behavior to improve future searches:
- Swipe patterns (what features do you like?)
- Search history (typical destinations, budgets)
- Automatically incorporates learned preferences into future searches

### 5. Auto-Organized Favorites

Saved listings automatically sorted by:
- Original search relevance
- Learned preferences
- Current availability
- Price changes

---

## Technical Architecture

### Frontend Stack
- **Framework**: Next.js 16 (App Router)
- **Language**: TypeScript, React 19
- **Styling**: Tailwind 4
- **Key Libraries**: Framer Motion (swipe gestures), Vapi SDK (voice)

**Key Routes**:
- `/discover/text` - Text-based conversational search
- `/discover/voice` - Voice input with push-to-talk
- `/discover/swipe` - Tinder-esque property cards
- `/sell` - Host listing creation (simulated)

### Backend Stack
- **Framework**: FastAPI (Python)
- **Validation**: Pydantic models
- **Architecture**: Service-oriented with dependency injection

**Core Services**:

**ConversationService** (`services/conversation_service.py`)
- Multi-turn dialogue management
- Parameter extraction using Claude
- Follow-up question generation
- State tracking across conversation

**VisionService** (`services/vision_service.py`)
- Image quality scoring
- Amenity detection from photos
- Multi-image analysis

**LettaService** (`services/letta_service.py`)
- User memory and context
- Preference learning from swipe patterns
- Historical search patterns

**PricingService** (`services/pricing_service.py`)
- Market rate analysis
- Competitive pricing recommendations
- Calendar-based pricing strategies

**VoiceService** (`services/voice_service.py`)
- Audio transcription via Groq Whisper
- Push-to-talk interface support

### Search & Data Layer

**ElasticClient** (`utils/elastic_client.py`)
- Vector embeddings for semantic search
- Hybrid search (semantic + keyword + filters)
- Geo-radius filtering
- Relevance scoring

**SupabaseClient** (`utils/supabase_client.py`)
- PostgreSQL database (listings, users, swipes)
- Object storage (images)
- User session management

**Geocoding** (`utils/geocoding.py`)
- City name → GPS coordinates
- Dynamic radius calculation by city size
- 40+ US cities mapped

### AI Models

**Anthropic Claude Sonnet 4.5**
- Conversational search dialogue
- Parameter extraction
- Content generation
- Vision analysis

**Groq**
- Fast parameter extraction (Llama)
- Audio transcription (Whisper)
- Real-time inference requirements

**Vapi**
- Voice synthesis (11labs)
- Speech-to-speech interface
- Push-to-talk implementation

**Elasticsearch Vector Search**
- Semantic embeddings
- K-NN similarity search
- Filtered vector queries

### Key API Endpoints

**Search Flow**:
```
POST /api/search/conversation
- Input: user message, conversation history, extracted params so far
- Output: assistant response, updated params, missing params, search status

POST /api/search/execute
- Input: extracted params, relevance threshold
- Output: ranked listings above threshold

POST /api/voice-to-text
- Input: audio file (webm)
- Output: transcribed text
```

**User Tracking**:
```
POST /api/swipe
- Input: user_id, listing_id, action (like/pass), metadata
- Output: success confirmation
- Side effect: Updates Letta with preference data

GET /api/saved-listings/{user_id}
- Output: Auto-ranked saved listings with reasoning
```

**Voice Interface**:
```
GET /api/vapi/publi

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 122 recognized source files, 959 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 156)

```
.gitignore
backend/.env.example
backend/activate_claude_search.py
backend/agents/fetch_agents/__init__.py
backend/agents/fetch_agents/booking_agent.py
backend/agents/fetch_agents/local_guide_agent.py
backend/agents/fetch_agents/PRICING_AGENT_README.md
backend/agents/fetch_agents/pricing_agent.py
backend/agents/fetch_agents/QA_AGENT_README.md
backend/agents/fetch_agents/qa_agent.py
backend/agents/fetch_agents/README.md
backend/agents/fetch_agents/register_agents.py
backend/agents/fetch_agents/register_booking_agent.py
backend/agents/fetch_agents/register_local_guide_agent.py
backend/agents/fetch_agents/register_pricing_agent.py
backend/agents/fetch_agents/register_qa_agent.py
backend/agents/fetch_agents/register_review_agent.py
backend/agents/fetch_agents/register_search_agent.py
backend/agents/fetch_agents/review_agent.py
backend/agents/fetch_agents/SEARCH_AGENT_README.md
backend/agents/fetch_agents/search_agent.py
backend/agents/fetch_agents/vibe_check_ai.py
backend/AUTO_SCAN_GUIDE.md
backend/camera_scan.html
backend/camera_test_debug.html
backend/docs/RADIUS_IMPLEMENTATION.md
backend/generate_mock_listings.py
backend/main_extended.py
backend/main.py
backend/main.py.backup
backend/mcp_server.py
backend/monitoring/__init__.py
backend/monitoring/arize_logger.py
backend/PHONE_CAMERA_TESTING.md
backend/phone_test_simple.html
backend/phone_test.html
backend/REALTIME_VIDEO_SETUP.md
backend/requirements-minimal.txt
backend/requirements.txt
backend/routes/search_routes.py
backend/seed_data.py
backend/services/__init__.py
backend/services/conversation_service.py
backend/services/csv_search_service.py
backend/services/elastic_agent_builder_service.py
backend/services/geocoding_service.py
backend/services/groq_service.py
backend/services/image_filter_service.py
backend/services/letta_service.py
backend/services/preference_analysis_service.py
backend/services/pricing_service.py
backend/services/qa_service.py
backend/services/saved_listings_service.py
backend/services/search_service.py
backend/services/seller_chatbot_service.py
backend/services/vapi_service.py
backend/services/vision_service.py
backend/services/voice_service.py
backend/services/yolo_service_OLD.py
backend/services/yolo_service.py
backend/setup_https.sh
backend/test_geo_direct.py
backend/test_geo_search.py
backend/test_geographic_pricing.py
backend/test_integration_quick.py
backend/test_summary.md
backend/test_yolo_standalone.py
backend/utils/__init__.py
backend/utils/elastic_client.py
backend/utils/geocoding.py
backend/utils/supabase_client.py
backend/VOICE_INTEGRATION.md
CLAUDE_CSV_SEARCH_README.md
CLAUDE.md
DEMO_GUIDE.md
docs/BRAINSTORM.md
docs/SPONSOR_REQUIREMENTS.md
docs/TASKS.md
frontend/.gitignore
frontend/app/components/PropertyMap.tsx
frontend/app/components/ui/listing-card-dynamic.tsx
frontend/app/components/ui/listing-card.tsx
frontend/app/components/ui/listing.tsx
frontend/app/components/ui/voice-search-button.tsx
frontend/app/discover/multimodal/page.tsx
frontend/app/discover/multimodal/results/page.tsx
frontend/app/discover/page.tsx
frontend/app/discover/swipe/page.tsx
frontend/app/discover/swipe/wishlist/page.tsx
frontend/app/discover/text/page.tsx
frontend/app/discover/voice/page.tsx
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/listing-detail/[id]/page.tsx
frontend/app/listing/[id]/confirm/page.tsx
frontend/app/listing/[id]/page.tsx
frontend/app/listing/confirm-pay/[id]/page.tsx
frontend/app/listing/confirm-pay/page.tsx
frontend/app/listing/page.tsx
frontend/app/map/page.tsx
frontend/app/page.tsx
frontend/app/preferences/activities/page.tsx
frontend/app/preferences/places/page.tsx
frontend/app/preferences/trip-type/page.tsx
frontend/app/sell/create/page.tsx
frontend/app/sell/page.tsx
frontend/app/sell/review/page.tsx
frontend/app/sell/review/success/page.tsx
frontend/app/sell/scan/page.tsx
frontend/app/seller-profile/page.tsx
frontend/app/user-profile/page.tsx
frontend/app/user-profile/trips/page.tsx
frontend/components.json
frontend/eslint.config.mjs
frontend/lib/api.ts
frontend/lib/types.ts
frontend/lib/utils.ts
frontend/next.config.ts
frontend/package.json
frontend/postcss.config.mjs
[36 more files omitted for size]
```

### Dependencies

- backend/requirements.txt: aiohttp, anthropic, arize-phoenix, cosmpy, elasticsearch, fastapi, groq, httpx, letta, numpy, openai, opencv-python, openinference-instrumentation-anthropic, opentelemetry-api, opentelemetry-sdk, pillow, psycopg2-binary, pydantic, pydantic-settings, python-dotenv, python-multipart, requests, supabase, toolhouse, uagents, ultralytics, uvicorn[standard]
- frontend/package.json: @emotion/react@^11.14.0, @emotion/styled@^11.14.1, @mui/icons-material@^7.3.4, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, autoprefixer@^10.4.21, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@16.0.0, framer-motion@^12.23.24, lucide-react@^0.548.0, next@16.0.0, postcss@^8.5.6, react@19.2.0, react-dom@19.2.0, tailwind-merge@^3.3.1, tailwindcss@^4.1.16, tw-animate-css@^1.4.0, typescript@^5
- package.json: gl-matrix@^3.4.4

### Recent commits (newest first)

- Add Elastic AI Agent Builder + Fix Vapi voice integration
- Fix seller listing page: token limits, date validation, map updates
- Add Vapi public key endpoint for voice features
- Merge branch 'main' of https://github.com/thenerd31/vibe
- Update GitHub URL to reflect repository rename to homi
- Update voice assistant name to Homi
- Update project branding from VIBE to Homi
- Add testing guide and integration test script
- Integrate clean-branch features while keeping scan functionality
- Add AI-powered listing generation pipeline with cross-origin data sharing
- Integrate scan data and photos into review page
- Merge main into seller-listings with object detection features
- resolved merge conflicts manually
- Merge branch 'main' of https://github.com/thenerd31/vibe into seller-listings
- Fix seller listing flow and add interactive maps
- clean branch without large CSV history
- Merge branch 'main' into object-detection-feature
- feat: finally cracked(urmom) object detection
- Update README with problem statement and key innovations
- Update README with problem statement and key innovations

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

### SETUP.md

```markdown
# 🚀 Quick Setup Guide

## For Team Members

### 1. Clone the Repo
```bash
git clone <REPO_URL>
cd vibe
```

### 2. Backend Setup (5 min)
```bash
cd backend
python3 -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
# Edit .env and add your API keys
```

**Get API Keys:**
- Anthropic: https://console.anthropic.com/
- Groq: https://console.groq.com/
- Supabase: https://supabase.com/dashboard
- Elastic: https://cloud.elastic.co/
- Letta: https://app.letta.ai/
- Others: See docs/SPONSOR_REQUIREMENTS.md

**Run Backend:**
```bash
uvicorn main:app --reload
# Runs on http://localhost:8000
```

### 3. Frontend Setup (5 min)
```bash
cd ../frontend
npm install
cp .env.example .env.local
# Add API keys
npm run dev
# Runs on http://localhost:3000
```

### 4. Pick Your Tasks
See `docs/TASKS.md` for task breakdown by person.

---

## Directory Structure

```
vibe/
├── backend/          ← FastAPI (Python)
├── frontend/         ← Next.js (TypeScript)
├── agents/           ← Fetch.ai agents
├── lens-studio/      ← Snap AR
├── docs/             ← All documentation
└── README.md         ← Start here!
```

---

## Quick Links

- **Main README**: [README.md](README.md)
- **Task Breakdown**: [docs/TASKS.md](docs/TASKS.md)
- **Brainstorming**: [docs/BRAINSTORM.md](docs/BRAINSTORM.md)
- **Sponsor Checklist**: [docs/SPONSOR_REQUIREMENTS.md](docs/SPONSOR_REQUIREMENTS.md)

---

## Need Help?

1. Check docs/ folder
2. Ask in team Discord
3. Check sponsor Discord servers
4. Google / Claude / ChatGPT

---

Let's build VIBE! 🌟

```

### TESTING_GUIDE.md

```markdown
# 🧪 VIBE Testing Guide
Complete testing guide for seller-listings + clean-branch integration

## ✅ What's Working (Verified)

### Seller-Listings Features (Your Primary Work)
- ✅ **Health check** - Backend running
- ✅ **Geocoding API** - Location services working
- ✅ **AI Analysis** - Generating titles ($250/night, amenities: kitchen, tv)
- ✅ **Voice endpoint** - Available for transcription
- ✅ **WebSocket scanning** - Real-time YOLO detection
- ✅ **Camera scan page** - Accessible at port 8000
- ✅ **Photo capture** - Auto-capture every 3s
- ✅ **Session management** - Store/retrieve scan data
- ✅ **Frontend pages** - All compiling successfully

### Clean-Branch Features (Integrated)
- ✅ **CSV Search Service** - Claude-powered CSV search
- ✅ **New listing pages** - listing-detail/[id], confirm-pay/[id]
- ✅ **Dynamic listing card** - New component
- ✅ **Fetch.ai agents** - Additional agent configs

---

## 🎯 Quick Test Plan (5 minutes)

### Test 1: Full Scan Flow (Most Important!)

**Steps:**
1. Open browser: http://localhost:3000/sell
2. Click the scan button (camera icon)
3. In camera_scan.html:
   - Click "Start Scanning"
   - Allow camera
   - Point at rooms/objects
   - Wait for 3-6 photos
   - Click "Stop & Finalize"
4. Review page should load with:
   - ✅ Auto-generated title
   - ✅ Suggested price ($180-250)
   - ✅ Detected amenities
   - ✅ Photos loaded
5. Edit location (click to edit)
   - ✅ Map updates
6. Click "Publish Listing"
7. Success page shows:
   - ✅ Cover photo
   - ✅ All listing details

**Expected Backend Logs:**
```
✅ YOLO service initialized with yolov8n.pt
📱 Phone connected to WebSocket
📸 Photo captured! Total: 1 images, Room: kitchen
📸 Photo captured! Total: 2 images, Room: living_room
✅ Session xxx finalized - 21 frames, 5 amenities
💾 Stored scan data for session xxx
📤 Retrieved scan data for session xxx
🔍 Analyzing scan: apartment, 1BR/1BA
📝 Generated title: Cozy SoMa Apartment in SF
💰 Suggested price: $180/night
```

### Test 2: Individual Endpoints

**Run these curl commands:**

```bash
# 1. Health check
curl http://localhost:8000/health
# Expected: {"status": "healthy", ...}

# 2. Geocoding
curl "http://localhost:8000/api/geocode?location=San%20Francisco"
# Expected: {latitude, longitude, formatted_address}

# 3. AI Analysis
curl -X POST http://localhost:8000/api/analyze-scan \
  -H "Content-Type: application/json" \
  -d '{
    "scan_data": {
      "amenities_detected": ["TV", "kitchen", "seating"],
      "property_type": "apartment",
      "bedrooms": 2,
      "bathrooms": 1
    },
    "location": "San Francisco"
  }'
# Expected: {success: true, title: "...", suggested_price: 180, amenities: [...]}
```

### Test 3: Frontend Pages

**Visit these URLs:**
- http://localhost:3000/ (landing)
- http://localhost:3000/sell (seller page) ✅
- http://localhost:3000/sell/review (review page) ✅
- http://localhost:3000/sell/review/success (success page) ✅
- http://localhost:3000/listing-detail/test-id (new page) ✅
- http
[truncated — 3341 more characters]
```

### package.json

```
{
  "dependencies": {
    "gl-matrix": "^3.4.4"
  }
}

```

### backend/requirements.txt

```
# Core Framework
fastapi
uvicorn[standard]
python-multipart
pydantic
pydantic-settings

# AI & ML
anthropic
groq
letta
openai

# Fetch.ai Agents
uagents
cosmpy

# Vector Search & Database
elasticsearch
supabase
psycopg2-binary

# Tool Management
toolhouse

# Voice
requests

# Observability
arize-phoenix
openinference-instrumentation-anthropic
opentelemetry-api
opentelemetry-sdk

# Image Processing
pillow
opencv-python
ultralytics
numpy

# Utilities
python-dotenv
httpx
aiohttp

```

### frontend/package.json

```
{
  "name": "ai_airbnb",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@emotion/react": "^11.14.0",
    "@emotion/styled": "^11.14.1",
    "@mui/icons-material": "^7.3.4",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.23.24",
    "lucide-react": "^0.548.0",
    "next": "16.0.0",
    "react": "19.2.0",
    "react-dom": "19.2.0",
    "tailwind-merge": "^3.3.1"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "autoprefixer": "^10.4.21",
    "eslint": "^9",
    "eslint-config-next": "16.0.0",
    "postcss": "^8.5.6",
    "tailwindcss": "^4.1.16",
    "tw-animate-css": "^1.4.0",
    "typescript": "^5"
  }
}

```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

// fonts
const hind = localFont({
  src: [
    {
      path: "../public/fonts/hind-light.otf",
      weight: "300",
      style: "normal",
    },
    {
      path: "../public/fonts/hind-regular.otf",
      weight: "400",
      style: "normal",
    },
    {
      path: "../public/fonts/hind-medium.otf",
      weight: "500",
      style: "normal",
    },
    {
      path: "../public/fonts/hind-semibold.otf",
      weight: "600",
      style: "normal",
    },
    {
      path: "../public/fonts/hind-bold.otf",
      weight: "700",
      style: "normal",
    },
  ],
  variable: "--font-hind",
});

const melodrame = localFont({
  src: "../public/fonts/relationship-of-mélodrame.ttf",
  variable: "--font-melodrame",
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        {/* Vapi Web SDK - Voice AI Integration */}
        <script src="https://cdn.jsdelivr.net/npm/@vapi-ai/web@latest/dist/index.js"></script>
      </head>
      <body
        className={`${geistSans.variable} ${geistMono.variable} ${hind.variable} ${melodrame.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### frontend/app/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function Home() {
  const router = useRouter();
  const [mode, setMode] = useState<'guest' | 'host'>('guest');

  const isGuest = mode === 'guest';

  const handleProceed = () => {
    if (isGuest) {
      router.push('/discover');
    } else {
      router.push('/sell');
    }
  };

  return (
    <div
      className={`min-h-screen flex flex-col transition-colors duration-500 ${
        isGuest ? 'bg-[#dfded3]' : 'bg-[#1a1a1a]'
      }`}
    >
      {/* Header */}
      <div className="pt-12 px-6 text-center">
        <h1 className={`text-4xl font-melodrame italic mb-4 transition-colors duration-500 ${
          isGuest ? 'text-stone-900' : 'text-[#E8E4D9]'
        }`}>
          What Brings You Here?
        </h1>
      </div>

      {/* Toggle Buttons Container */}
      <div className="flex items-center justify-center px-6 mt-32">
        <div className="flex gap-8">
          {/* Guest Button */}
          <button
            type="button"
            onClick={() => setMode('guest')}
            className={`w-40 h-40 rounded-3xl transition-all duration-300 flex items-center justify-center ${
              isGuest
                ? 'bg-black border-4 border-black'
                : 'bg-transparent border-2 border-[#E8E4D9]'
            }`}
            aria-label="Guest mode"
          >
            <span className={`text-3xl font-melodrame italic transition-colors duration-300 ${
              isGuest ? 'text-[#E8E4D9]' : 'text-[#E8E4D9]'
            }`}>
              Guest
            </span>
          </button>

          {/* Host Button */}
          <button
            type="button"
            onClick={() => setMode('host')}
            className={`w-40 h-40 rounded-full transition-all duration-300 flex items-center justify-center ${
              !isGuest
                ? 'bg-[#E8E4D9] border-4 border-[#E8E4D9]'
                : 'bg-transparent border-4 border-transparent ring-2 ring-black-500'
            }`}
            aria-label="Host mode"
          >
            <span className={`text-3xl font-melodrame italic transition-colors duration-300 ${
              !isGuest ? 'text-black' : 'text-black'
            }`}>
              Host
            </span>
          </button>
        </div>
      </div>

      {/* Description */}
      <div className="flex-1 flex flex-col items-center justify-start px-6 mt-12">
        <div className="text-center max-w-md">
          <p className={`text-xl font-hind mb-2 transition-colors duration-500 ${
            isGuest ? 'text-stone-800' : 'text-[#E8E4D9]'
          }`}>
            {isGuest ? 'Describe your dream home' : 'Scan your room'}
          </p>
          <p className={`text-xl font-hind transition-colors duration-500 ${
            isGuest ? 'text-stone-800' : 'text-[#E8E4D9]'
          }`}>
            {isGuest
              ? "We'll find spaces that match your vibe"
              : "We'll turn it into a beautiful listing"
            }
          </p>
        </div>

        {/* Liquid Glass Arrow Button */}
        <button
          type="button"
          onClick={handleProceed}
          className={`mt-40 w-20 h-20 rounded-full bg-white/20 backdrop-blur-xl border border-white/30 hover:bg-white/30 hover:scale-110 transition-all duration-300 flex items-center justify-center shadow-lg active:scale-95`}
          aria-label="Proceed"
        >
          <svg
            className={`w-8 h-8 transition-colors duration-500 ${
              isGuest ? 'text-white' : 'text-white'
            }`}
            fill="none"
            stroke="currentColor"
            strokeWidth="2.5"
            viewBox="0 0 24 24"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
            />
          </svg>
        </button>
      </div>
    </div>
  );
}

```

### frontend/app/discover/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Menu, X, Pencil, Mic, Sparkles } from 'lucide-react';
import Image from 'next/image';

// TODO: add idea on top, gradient under for contrast
export default function DiscoverPage() {
  const router = useRouter();
  const [sidebarOpen, setSidebarOpen] = useState(false);

  return (
    <div className="min-h-screen flex flex-col relative bg-[#DFDFD3]">
      <button
        type="button"
        onClick={() => setSidebarOpen(true)}
        className="absolute top-6 right-6 z-30 p-2 text-stone-900 hover:bg-stone-300 rounded-full transition-colors"
        aria-label="Open menu"
      >
        <Menu className="w-6 h-6" />
      </button>

      {/* header */}
      <div className="pt-12 px-6 text-center">
        <h1 className="text-4xl font-melodrame italic text-stone-900 mb-4">
          Describe Your Perfect Place
        </h1>
      </div>

      {/* listing placeholder card */}
      <div className="flex-1 flex items-center justify-center px-6 pb-20">
        <div className="relative w-full max-w-md aspect-[8/16] rounded-3xl shadow-2xl overflow-hidden">
          {/* Background Image */}
          <Image
            src="/images/placeholder-card.png"
            alt="Placeholder"
            fill
            className="object-cover"
            priority
          />

          {/* Gradient overlay for better text readability */}
          <div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/40" />

          {/* Content */}
          <div className="relative h-full flex flex-col items-start justify-center p-8">
            <div className="flex-1 flex items-center">
              <div className="text-white">
                <p className="text-lg mb-2 font-hind font-regular">Welcome!</p>
                <h2 className="text-3xl font-semibold leading-tight">
                  Where would you like to live?
                </h2>
              </div>
            </div>

            {/* input buttons */}
            <div className="w-full flex justify-center gap-6 pb-8">
            <button
              type="button"
              onClick={() => router.push('/discover/text')}
              className="w-16 h-16 rounded-full bg-white/20 backdrop-blur-xl border border-white/30 hover:bg-white/30 hover:scale-110 transition-all duration-300 flex items-center justify-center shadow-lg active:scale-95"
              aria-label="Type your preference"
            >
              <Pencil className="w-6 h-6 text-white" strokeWidth={2} />
            </button>

            <button
              type="button"
              onClick={() => router.push('/discover/voice')}
              className="w-20 h-20 rounded-full bg-white/20 backdrop-blur-xl border border-white/30 hover:bg-white/30 hover:scale-110 transition-all duration-300 flex items-center justify-center shadow-lg active:scale-95"
              aria-label="Speak your preference"
            >
              <Mic className="w-8 h-8 text-white" strokeWidth={2} />
            </button>

            <button
              type="button"
              onClick={() => router.push('/discover/multimodal')}
              className="w-16 h-16 rounded-full bg-white/20 backdrop-blur-xl border border-white/30 hover:bg-white/30 hover:scale-110 transition-all duration-300 flex items-center justify-center shadow-lg active:scale-95"
              aria-label="Upload images"
            >
              <Sparkles className="w-6 h-6 text-white" strokeWidth={2} />
            </button>
            </div>
          </div>
        </div>
      </div>

      {/* Dark overlay */}
      {sidebarOpen && (
        <div
          className="fixed inset-0 bg-black/50 z-40 transition-opacity"
          onClick={() => setSidebarOpen(false)}
        />
      )}

      {/* Sidebar */}
      <div
        className={`fixed top-0 right-0 h-full w-64 bg-[#DFDFD3] shadow-2xl z-50 transform transition-transform duration-300 ease-in-out ${
          sidebarOpen ? 'translate-x-0' : 'translate-x-full'
        }`}
      >
        {/* Close button */}
        <button
          type="button"
          onClick={() => setSidebarOpen(false)}
          className="absolute top-6 left-6 p-2 text-stone-900 hover:bg-stone-200 rounded-full transition-colors"
          aria-label="Close menu"
        >
          <X className="w-6 h-6" />
        </button>

        {/* Menu items */}
        <nav className="pt-24 px-8 flex flex-col h-full">
          <ul className="space-y-8 flex-1">
            <li>
              <button
                type="button"
                onClick={() => {
                  router.push('/discover');
                  setSidebarOpen(false);
                }}
                className="text-4xl font-melodrame italic text-stone-900 hover:text-stone-600 hover:bg-black/10 transition-all px-4 py-2 rounded-lg w-full text-left"
              >
                Discover
              </button>
            </li>
            <li>
              <button
                type="button"
                onClick={() => {
                  router.push('/discover/swipe/wishlist');
                  setSidebarOpen(false);
                }}
                className="text-4xl font-melodrame italic text-stone-900 hover:text-stone-600 hover:bg-black/10 transition-all px-4 py-2 rounded-lg w-full text-left"
              >
                Wishlist
              </button>
            </li>
            <li>
              <button
                type="button"
                onClick={() => {
                  router.push('/map');
                  setSidebarOpen(false);
                }}
                className="text-4xl font-melodrame italic text-stone-900 hover:text-stone-600 hover:bg-black/10 transition-all px-4 py-2 rounded-lg w-full text-left"
              >
                Map
              </button>
            </li>
            <li>
              <button
        
[truncated — 1760 more characters]
```

### frontend/app/sell/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Menu, X, ScanLine, Heart } from 'lucide-react';
import StarIcon from "@mui/icons-material/Star";
import Image from 'next/image';

// TODO: change hardcoded data
const USER_LISTINGS = [
  {
    id: 1,
    title: 'Penthouse Loft in SF',
    guests: 5,
    bedrooms: 1,
    bathrooms: 1,
    rating: 4.99,
    image: '/images/golden-gateway/golden-gateway1.avif',
  },
  {
    id: 2,
    title: 'Entire Suite in Los Angeles',
    guests: 2,
    bedrooms: 1,
    bathrooms: 1,
    rating: 4.97,
    image: '/images/morrocan-home/morrocan-home1.avif',
  },
  {
    id: 3,
    title: 'Treehouse in Brea',
    guests: 5,
    bedrooms: 1,
    bathrooms: 1,
    rating: 4.99,
    image: '/images/ritzy-room/ritzy-room1.avif',
  },
  {
    id: 4,
    title: 'Tiny Home in Topanga',
    guests: 2,
    bedrooms: 1,
    bathrooms: 1,
    rating: 4.97,
    image: '/images/victorian-home/victorian_home1.avif',
  },
];

export default function SellPage() {
  const router = useRouter();
  const [sidebarOpen, setSidebarOpen] = useState(false);

  return (
    <div className="min-h-screen bg-black relative">
      <button
        type="button"
        onClick={() => setSidebarOpen(true)}
        className="absolute top-6 right-6 z-30 p-2 text-white hover:bg-stone-800 rounded-full transition-colors"
        aria-label="Open menu"
      >
        <Menu className="w-6 h-6" />
      </button>

      {/* header */}
      <div className="pt-12 px-6">
        <h1 className="text-4xl font-melodrame italic text-white mb-4">
          Start Listing
        </h1>
      </div>

      {/* placeholder */}
      <div className="px-6 pb-8">
        <div className="relative w-full max-w-md mx-auto aspect-[8/16] rounded-3xl shadow-2xl overflow-hidden bg-gradient-to-b from-stone-300 to-stone-500">
          <Image
            src="/images/placeholder-card.png"
            alt="Start Listing"
            fill
            className="object-cover"
            priority
          />

          {/* gradient overlay */}
          <div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/40" />

          {/* content */}
          <div className="relative h-full flex flex-col items-start justify-center p-8">
            <div className="flex-1 flex items-center">
              <div className="text-white">
                <p className="text-lg mb-2 font-hind font-light">Ready to Host?</p>
                <h2 className="text-3xl font-semibold leading-tight mb-1">
                  Let's Turn Your Place
                </h2>
                <h2 className="text-3xl font-semibold leading-tight">
                  into Stay with <span className="font-melodrame italic">Artificial Intelligence</span>
                </h2>
              </div>
            </div>

            {/* action buttons */}
            <div className="flex justify-center pb-8 mx-auto">
              <button
                type="button"
                onClick={() => window.location.href = 'http://localhost:8000/camera_scan.html'}
                className="w-16 h-16 rounded-full bg-white/20 backdrop-blur-xl border border-white/30 hover:bg-white/30 hover:scale-110 transition-all duration-300 flex items-center justify-center shadow-lg active:scale-95"
                aria-label="Scan with Phone"
              >
                <ScanLine className="w-6 h-6 text-white" strokeWidth={2} />
              </button>
            </div>
          </div>
        </div>
      </div>

      {/* listings */}
      {/* TODO: empty listing? cool to demo making a new listing using glasses */}
      <div className="px-6 pb-24">
        <h2 className="text-3xl font-melodrame italic text-white mb-6">
          Your Listings
        </h2>

        {/* Pinterest-style Masonry Grid */}
        <div className="grid grid-cols-2 gap-4">
          {USER_LISTINGS.map((listing) => (
            <div
              key={listing.id}
              className="relative rounded-2xl overflow-hidden shadow-lg cursor-pointer hover:scale-105 transition-transform duration-300"
              onClick={() => router.push(`/listing`)}
            >
              {/* Image */}
              <div className="relative w-full aspect-[3/4]">
                <Image
                  src={listing.image}
                  alt={listing.title}
                  fill
                  className="object-cover"
                />

                <button
                  type="button"
                  className="absolute top-3 right-3 p-2 rounded-full bg-black/30 backdrop-blur-sm hover:bg-black/50 transition-colors"
                  onClick={(e) => {
                    e.stopPropagation();
                    console.log('Favorited:', listing.id);
                  }}
                  aria-label="Favorite listing"
                >
                  <Heart className="w-5 h-5 text-white" strokeWidth={2} />
                </button>
              </div>

              {/* Info */}
              <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 via-black/60 to-transparent p-4">
                <h3 className="text-white font-semibold text-base mb-1 line-clamp-1">
                  {listing.title}
                </h3>
                <p className="text-white/80 text-sm mb-2">
                  {listing.guests} guests, {listing.bedrooms} bedroom{listing.bedrooms > 1 ? 's' : ''}, {listing.bathrooms} bath{listing.bathrooms > 1 ? 's' : ''}
                </p>
                <div className="flex items-center gap-1">
                  <span className="text-white text-sm font-semibold"></span>
                  <span className="text-white text-sm font-semibold">{listing.rating} <StarIcon sx={{ fontSize: "0.85em", color: "#facc15", verticalAlign: "middle" }} /> </span>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>

      
[truncated — 2695 more characters]
```

### frontend/app/seller-profile/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Menu, X, Star } from 'lucide-react';
import StarIcon from "@mui/icons-material/Star";
import Image from 'next/image';

// TODO: Replace with actual seller data from API/auth
const SELLER_DATA = {
  name: 'Qian',
  role: 'Host',
  avatar: '/images/placeholder-card.png', // Replace with actual avatar
  listings: 4,
  reviews: 12,
  rating: 4.98,
  yearsHosting: 2,
};

// TODO: Replace with actual reviews data
// TODO: add change profile button and messages
const SELLER_REVIEWS = [
  {
    id: 1,
    guestName: 'Sarah M.',
    guestAvatar: '/images/peaceful-private-room-B-3-min-walk-to-ocean-beach/ocean_beach_1.avif',
    rating: 5.0,
    date: 'January 2025',
    comment: 'Amazing place! The listing was exactly as described. Qian was a wonderful host and very responsive.',
    propertyName: 'Penthouse Loft in SF',
  },
  {
    id: 2,
    guestName: 'Michael K.',
    guestAvatar: '/images/classic-private-room-in-west-LA/west_la_1.avif',
    rating: 4.9,
    date: 'December 2024',
    comment: 'Great stay! The space was clean and well-maintained. Would definitely recommend.',
    propertyName: 'Entire Suite in Los Angeles',
  },
  {
    id: 3,
    guestName: 'Emily R.',
    guestAvatar: '/images/suite-A-Azure-Anticipation/suite-1.avif',
    rating: 5.0,
    date: 'November 2024',
    comment: 'Fantastic experience! The amenities were top-notch and the location was perfect.',
    propertyName: 'Treehouse in Brea',
  },
];

export default function SellerProfilePage() {
  const router = useRouter();
  const [sidebarOpen, setSidebarOpen] = useState(false);

  return (
    <div className="min-h-screen bg-black relative pb-20">
      <button
        type="button"
        onClick={() => setSidebarOpen(true)}
        className="absolute top-6 right-6 z-30 p-2 text-white hover:bg-stone-800 rounded-full transition-colors"
        aria-label="Open menu"
      >
        <Menu className="w-6 h-6" />
      </button>

      {/* Header */}
      <div className="pt-12 px-6">
        <h1 className="text-4xl font-melodrame italic text-white mb-8">
          Profile
        </h1>
      </div>

      {/* Main Profile Card */}
      <div className="px-6 mb-8">
        <div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-3xl p-6 shadow-lg">
          {/* User Info Section */}
          <div className="flex items-start gap-6 mb-6">
            {/* Avatar */}
            <div className="relative w-24 h-24 rounded-full overflow-hidden ring-4 ring-white/30 shadow-lg flex-shrink-0">
              <Image
                src={SELLER_DATA.avatar}
                alt={SELLER_DATA.name}
                fill
                className="object-cover"
              />
            </div>

            {/* Stats Grid */}
            <div className="flex-1">
              <h2 className="text-2xl font-semibold text-white mb-1">
                {SELLER_DATA.name}
              </h2>
              <p className="text-white/70 font-hind mb-4">{SELLER_DATA.role}</p>

              {/* Stats */}
              <div className="grid grid-cols-3 gap-4">
                <div className="text-center">
                  <div className="text-2xl font-bold text-white">
                    {SELLER_DATA.listings}
                  </div>
                  <div className="text-xs text-white/60 font-hind">Listings</div>
                </div>
                <div className="text-center">
                  <div className="text-2xl font-bold text-white">
                    {SELLER_DATA.reviews}
                  </div>
                  <div className="text-xs text-white/60 font-hind">Reviews</div>
                </div>
                <div className="text-center">
                  <div className="text-2xl font-bold text-white">
                    {SELLER_DATA.yearsHosting}
                  </div>
                  <div className="text-xs text-white/60 font-hind">
                    Years Hosting
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Quick Actions - Just Reviews */}
      <div className="px-6 mb-8">
        <div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6 shadow-lg">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-4">
              <div className="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center">
                <Star className="w-6 h-6 text-white" />
              </div>
              <div>
                <div className="flex items-center gap-2">
                  <span className="text-2xl font-bold text-white">
                    {SELLER_DATA.rating}
                  </span>
                  <StarIcon sx={{ fontSize: "1.2em", color: "#facc15" }} />
                </div>
                <span className="text-sm font-hind text-white/70">
                  {SELLER_DATA.reviews} Reviews
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Reviews Section */}
      {SELLER_REVIEWS.length > 0 && (
        <div className="px-6">
          <h2 className="text-2xl font-melodrame italic text-white mb-6">
            Recent Reviews
          </h2>
          <div className="space-y-4">
            {SELLER_REVIEWS.map((review) => (
              <div
                key={review.id}
                className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-5 shadow-lg"
              >
                {/* Reviewer Info */}
                <div className="flex items-start gap-4 mb-3">
                  <div className="relative w-12 h-12 rounded-full overflow-hidden ring-2 ring-white/30 flex-shrink-0">
                    <Image
                      src={review.guestAvatar}
                      alt={review.guestName}
           
[truncated — 3991 more characters]
```

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