# Project export: FitCheck

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: AI stylist transforming voice + photo into instant clothing try-ons, smart color picks, and closet-based outfits, helping users save time while unlocking new upsell revenue for businesses.
- Devpost: https://devpost.com/software/stylo-xinpe8
- GitHub: https://github.com/gsuriya/CalHacks-25
- Video: https://www.youtube.com/embed/4KxLaVWTH7Y?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — null (19 commits), Neel Sharma (8 commits), Anay Shukla (2 commits)

## Devpost submission (written by the team)

### Inspiration

73% of online shoppers return clothes due to poor fit or color mismatch. Fashion waste contributes to 10% of global carbon emissions. We identified a critical gap in how people discover fashion. Traditional shopping apps show you products, but they don't understand YOU - your skin tone, your style preferences, or what actually looks good on you. We saw an urgent need for AI-powered personalization that goes beyond generic recommendations. We envisioned an intelligent system that truly understands YOUR unique features and preferences, making sustainable fashion choices effortless and personal.

### What it does

FitCheck revolutionizes fashion discovery through our sophisticated AI architecture: 🎤 Voice-Powered Discovery (VAPI + Gemini): Our custom VAPI tool transforms spoken requests like "show me casual red shirts under $50" into structured filters. VAPI processes voice input in real-time, and Gemini extracts user preferences with essentially 100% accuracy. 📸 AI Skin Tone Analysis (Gemini): Our computer vision pipeline uses Gemini's advanced capabilities to analyze skin tone from camera input, employing Korean skin tone classification methods for precise color matching across 30+ fashion colors. 👕 Virtual Try-On (Gemini + VAPI + FASHN API): Simply say "Give me full outfit recommendations" and seamlessly visualize how clothes look on you - whether it's clothes in your closet that you scanned in, new clothes discovered through filters, or a mix of both! Our integrated try-on system powered by Gemini's image generation capabilities brings your entire fashion universe together. 🤖 Intelligent Classification (Gemini): Every product is analyzed by Gemini to classify attributes like material, occasion, and style, enabling our multi-dimensional filtering system. ♻️ Sustainability Features: Smart duplicate detection alerts users when viewing items similar to their existing closet, reducing wasteful purchases. 📊 Smart Data Pipeline: Zenrows web scraping feeds our product database, while our Next.js frontend delivers a beautiful, swipe-based interface.

### Challenges we ran into

🔗 Complex API Integration: Orchestrating VAPI voice processing with Gemini's classification while maintaining real-time performance proved challenging. We had to implement smart caching and request batching to prevent API rate limiting while ensuring smooth user experience. 🎨 Accurate Skin Tone Analysis: Building reliable color extraction from photos across different lighting conditions, camera qualities, and skin tones required extensive testing. We implemented multiple fallback methods and color space conversions to ensure consistent results. 🗣️ Voice-to-Filter Translation: Converting natural language like "show me casual summer dresses under $100" into structured filter objects was complex. We had to prompt engineer Gemini extensively to reliably extract colors, price ranges, occasions, and other attributes from conversational speech.

### Accomplishments we're proud of

Advanced Gemini Integration: Leveraged Gemini across four critical functions - skin tone analysis using computer vision, intelligent product classification across multiple attributes, voice command interpretation for filter extraction, and powering our virtual try-on system. This multi-faceted approach maximized Gemini's versatility and created seamless AI experiences. Advanced Gemini Integration: Leveraged Gemini across four critical functions - skin tone analysis using computer vision, intelligent product classification across multiple attributes, voice command interpretation for filter extraction, and powering our virtual try-on system. This multi-faceted approach maximized Gemini's versatility and created seamless AI experiences. Custom VAPI Voice Tool: Built our own sophisticated voice processing system on top of VAPI's infrastructure, creating natural language understanding that reliably extracts complex fashion filters from conversational speech. Our custom implementation transforms casual voice commands into precise product searches in real-time. Custom VAPI Voice Tool: Built our own sophisticated voice processing system on top of VAPI's infrastructure, creating natural language understanding that reliably extracts complex fashion filters from conversational speech. Our custom implementation transforms casual voice commands into precise product searches in real-time. Measurable Sustainability Impact: Our duplicate detection algorithm actively prevents wasteful purchases by intelligently comparing new items against users' existing closets, directly reducing fashion waste and saving users money through smart recommendations. Measurable Sustainability Impact: Our duplicate detection algorithm actively prevents wasteful purchases by intelligently comparing new items against users' existing closets, directly reducing fashion waste and saving users money through smart recommendations. Optimized Multi-API Efficiency: Streamlined fashion discovery through strategic integration of VAPI for voice processing, Gemini for AI intelligence, FASHN API for virtual try-on, and custom LLM workflows. This coordinated approach reduced traditional shopping decision time from 23 minutes to under 3 minutes while improving accuracy. Optimized Multi-API Efficiency: Streamlined fashion discovery through strategic integration of VAPI for voice processing, Gemini for AI intelligence, FASHN API for virtual try-on, and custom LLM workflows. This coordinated approach reduced traditional shopping decision time from 23 minutes to under 3 minutes while improving accuracy.

### What we learned

🤖 AI Integration Complexity: Orchestrating VAPI's voice processing with Gemini's vision and classification required careful prompt engineering and fallback mechanisms for reliable multi-modal experiences. 🎨 Fashion Color Science: Accurate color matching involves undertones, lighting conditions, and cultural classification methods - far more nuanced than simple hex comparisons. 🗣️ Voice vs. Text Patterns: Users speak about fashion conversationally through VAPI, requiring different natural language processing approaches than traditional text input. ⚡ Performance Trade-offs: Balancing real-time AI responses with accuracy taught us when to cache, batch API calls, and prioritize user experience over perfect precision. ♻️ Behavioral AI Design: Subtle AI nudges (like duplicate alerts) drive sustainable behavior more effectively than restrictive approaches.

### What's next

Enhanced Gemini integration for outfit coordination Enhanced Gemini integration for outfit coordination VAPI conversation memory for personalized shopping assistants VAPI conversation memory for personalized shopping assistants Expanded virtual try-on with Gemini's latest models Expanded virtual try-on with Gemini's latest models Real-time trend analysis using our scraped data Real-time trend analysis using our scraped data

## README (from the GitHub repository)

# Fashion stylist app

*Automatically synced with your [v0.dev](https://v0.dev) deployments*

[![Deployed on Vercel](https://img.shields.io/badge/Deployed%20on-Vercel-black?style=for-the-badge&logo=vercel)](https://vercel.com/suriyas-projects-8944df7c/v0-fashion-stylist-app)
[![Built with v0](https://img.shields.io/badge/Built%20with-v0.dev-black?style=for-the-badge)](https://v0.dev/chat/projects/dQgj1ZR83Ce)

## Overview

This repository will stay in sync with your deployed chats on [v0.dev](https://v0.dev).
Any changes you make to your deployed app will be automatically pushed to this repository from [v0.dev](https://v0.dev).

## Deployment

Your project is live at:

**[https://vercel.com/suriyas-projects-8944df7c/v0-fashion-stylist-app](https://vercel.com/suriyas-projects-8944df7c/v0-fashion-stylist-app)**

## Build your app

Continue building your app on:

**[https://v0.dev/chat/projects/dQgj1ZR83Ce](https://v0.dev/chat/projects/dQgj1ZR83Ce)**

## How It Works

1. Create and modify your project using [v0.dev](https://v0.dev)
2. Deploy your chats from the v0 interface
3. Changes are automatically pushed to this repository
4. Vercel deploys the latest version from this repository


## Detected evidence (automated analysis)

Indexed codebase: 89 recognized source files, 517 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
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (97 of 97)

```
.gitignore
app/api/try-on/[id]/route.ts
app/api/try-on/multi/route.ts
app/api/try-on/route.ts
app/closet/loading.tsx
app/closet/page.tsx
app/color-analysis/page.tsx
app/community/page.tsx
app/components/AnimatedBackground.tsx
app/components/BottomTabBar.tsx
app/components/VoiceAgent.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/swipe/page.tsx
app/try-on/[productId]/page.tsx
app/try-on/multi/page.tsx
app/wrapped/page.tsx
components.json
components/theme-provider.tsx
components/TranscriptBox.tsx
components/ui/accordion.tsx
components/ui/alert-dialog.tsx
components/ui/alert.tsx
components/ui/aspect-ratio.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/breadcrumb.tsx
components/ui/button.tsx
components/ui/calendar.tsx
components/ui/card.tsx
components/ui/carousel.tsx
components/ui/chart.tsx
components/ui/checkbox.tsx
components/ui/collapsible.tsx
components/ui/command.tsx
components/ui/context-menu.tsx
components/ui/dialog.tsx
components/ui/drawer.tsx
components/ui/dropdown-menu.tsx
components/ui/form.tsx
components/ui/hover-card.tsx
components/ui/input-otp.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/menubar.tsx
components/ui/navigation-menu.tsx
components/ui/pagination.tsx
components/ui/popover.tsx
components/ui/progress.tsx
components/ui/radio-group.tsx
components/ui/resizable.tsx
components/ui/scroll-area.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/sidebar.tsx
components/ui/skeleton.tsx
components/ui/slider.tsx
components/ui/sonner.tsx
components/ui/switch.tsx
components/ui/table.tsx
components/ui/tabs.tsx
components/ui/textarea.tsx
components/ui/toast.tsx
components/ui/toaster.tsx
components/ui/toggle-group.tsx
components/ui/toggle.tsx
components/ui/tooltip.tsx
components/ui/use-mobile.tsx
components/ui/use-toast.ts
components/VapiVoiceWidget.tsx
FILTERS.md
hooks/use-mobile.tsx
hooks/use-toast.ts
lib/gemini-api.ts
lib/product-filters.ts
lib/skin-tone-analysis.ts
lib/skin-tone-storage.ts
lib/utils.ts
lib/vapi-config.ts
next.config.mjs
package.json
postcss.config.mjs
public/filter-options.json
public/filters-metadata.json
public/product-filters-map.json
README.md
scripts/generate-filters-map.js
src/voice/handleClosetRecommendation.ts
src/voice/handleFilterRequest.ts
src/voice/onTranscription.ts
styles/globals.css
tailwind.config.ts
test-gemini.js
tsconfig.json
VAPI_SETUP.md
```

### Dependencies

- package.json: @hookform/resolvers@^3.9.1, @radix-ui/react-accordion@latest, @radix-ui/react-alert-dialog@latest, @radix-ui/react-aspect-ratio@latest, @radix-ui/react-avatar@latest, @radix-ui/react-checkbox@latest, @radix-ui/react-collapsible@latest, @radix-ui/react-context-menu@latest, @radix-ui/react-dialog@latest, @radix-ui/react-dropdown-menu@latest, @radix-ui/react-hover-card@latest, @radix-ui/react-label@latest, @radix-ui/react-menubar@latest, @radix-ui/react-navigation-menu@latest, @radix-ui/react-popover@latest, @radix-ui/react-progress@latest, @radix-ui/react-radio-group@latest, @radix-ui/react-scroll-area@latest, @radix-ui/react-select@latest, @radix-ui/react-separator@latest, @radix-ui/react-slider@latest, @radix-ui/react-slot@latest, @radix-ui/react-switch@latest, @radix-ui/react-tabs@latest, @radix-ui/react-toast@latest, @radix-ui/react-toggle@latest, @radix-ui/react-toggle-group@latest, @radix-ui/react-tooltip@latest, @types/node@^22, @types/react@^19, @types/react-dom@^19, @vapi-ai/web@^2.3.6, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@latest, date-fns@4.1.0, embla-carousel-react@latest, input-otp@latest, lucide-react@^0.454.0, next@15.2.4, next-themes@latest, postcss@^8.5, react@^19, react-day-picker@latest, react-dom@^19, react-hook-form@latest, react-resizable-panels@latest, recharts@latest, sonner@latest, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@^5, vaul@latest, zod@^3.24.1

### Recent commits (newest first)

- Add FitCheck architecture diagram for DevPost submission
- closet try on rec done
- yo yo
- multi virtual try on done
- says similar clothing alr in closet
- filters work
- added pants
- pants + better closet UI + working pants try on
- Automatic filteration system COMPLETE LESGOOOOOOOO
- Fix Gemini API endpoint - update to correct model and API version
- Implement complete voice-to-filter system with VAPI + Gemini integration
- filters done
- ui better
- Fixed
- Fix skin tone analysis integration on home page
- Resolve merge conflict: Combine skin tone analysis with new filter system
- json added
- updated
- feat: Add skin tone analysis and color matching to swipe page
- Strategic merge: Integrate voice-agent features while preserving all main functionality

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

### VAPI_SETUP.md

```markdown
# 🎤 Fashion Voice Agent Setup

This project includes a sophisticated voice agent powered by Vapi that can interact with users about fashion topics. The voice agent is already configured with fashion-specific prompts and can provide styling advice, trend information, and outfit recommendations.

## 🚀 Quick Setup

### 1. Get Your Vapi API Key

1. Visit [Vapi Dashboard](https://dashboard.vapi.ai)
2. Sign up or log in to your account
3. Navigate to your API keys section
4. Copy your **Public API Key** (starts with `pk_`)

### 2. Configure Environment Variables

Create a `.env.local` file in your project root:

```bash
# Vapi Configuration
NEXT_PUBLIC_VAPI_API_KEY=pk_your_actual_vapi_public_key_here
```

### 3. Start the Application

```bash
pnpm dev
```

## 🎯 Features

### Voice Interactions

- **Natural Conversations**: Chat naturally about fashion topics
- **Real-time Transcription**: See your conversation in real-time
- **Fashion Expertise**: Ask about:
  - Current fashion trends
  - Styling advice
  - Outfit recommendations
  - Color coordination
  - Seasonal fashion tips
  - Fashion brand recommendations

### UI Features

- **Floating Widget**: Fixed position voice widget in bottom-right corner
- **Visual Feedback**:
  - Pulsing indicator when assistant is speaking
  - Loading states during connection
  - Real-time conversation transcript
- **Easy Controls**: Simple start/end call buttons
- **Modern Design**: Beautiful gradient UI with glassmorphism effects

## 🎨 Usage Examples

Try asking the fashion assistant:

- _"What are the latest fashion trends for this season?"_
- _"Can you help me style a casual outfit for a weekend brunch?"_
- _"What colors go well with navy blue?"_
- _"I have a job interview tomorrow, what should I wear?"_
- _"Tell me about sustainable fashion brands"_
- _"How can I accessorize a little black dress?"_

## 🔧 Technical Details

### Assistant Configuration

- **Assistant ID**: `a64ef464-f042-4243-911a-860cebd61506`
- **Specialized**: Pre-configured with fashion industry knowledge
- **Voice Provider**: 11Labs for natural speech
- **Speech Recognition**: Deepgram for accurate transcription

### Component Structure

```
components/
├── VapiVoiceWidget.tsx    # Main voice widget component
lib/
├── vapi-config.ts         # Configuration and helpers
```

### Key Features

- **Error Handling**: Graceful fallbacks for API issues
- **Loading States**: Visual feedback during connections
- **Responsive Design**: Works on desktop and mobile
- **TypeScript**: Full type safety
- **Real-time Events**: Live conversation updates

## 🛠 Troubleshooting

### Common Issues

**Voice Agent Not Working**

1. Check your API key is correctly set in `.env.local`
2. Ensure the key starts with `pk_` (public key)
3. Restart your development server after adding the key
4. Check browser console for error messages

**No Audio/Microphone Issues**

1. Grant microphone permissions when prompted
2. Check browser microphone settings
3. Ensure speakers/
[truncated — 952 more characters]
```

### FILTERS.md

```markdown
# Product Filter System

This document explains the product filter system implementation for the StyleAI application.

## Overview

The filter system creates a static JSON map of product attributes that enables efficient client-side filtering without repeated API calls. The system supports both database-backed filters (color, price, store, stock) and placeholder fields for future LLM-based analysis (material, occasion, season).

## Architecture

### Static Data Files

The system uses three static JSON files in the `data/` directory:

- **`product-filters-map.json`** - Main filters map keyed by product ID
- **`filter-options.json`** - Available filter values for UI components  
- **`filters-metadata.json`** - Generation timestamp and metadata

### JSON Map Structure

Each product in the filters map has this structure:

```json
{
  "<product_id>": {
    "color": "string",      // from database
    "price": "number",      // from database  
    "store": "string",      // from database (currently uses product type)
    "inStock": "number",    // exact stock count from database
    "material": "",         // placeholder for LLM integration
    "occasion": "",         // placeholder for LLM integration
    "season": ""            // placeholder for LLM integration
  }
}
```

## Usage

### 1. Initial Setup

The filter system loads instantly when the app starts:

```typescript
import { productFilterManager } from '../lib/product-filters'

// Filter system is ready immediately - no async initialization needed
const filtersMap = productFilterManager.getFiltersMap()
const filterOptions = productFilterManager.getFilterOptions()
```

### 2. Filtering Products

```typescript
// Filter an array of product IDs
const filteredIds = productFilterManager.filterProducts(productIds, activeFilters)

// Get product attributes
const attributes = productFilterManager.getProductAttributes(productId)
```

### 3. Available Filters

**Database-backed filters (working now):**
- **Color** - Product color from database
- **Price** - Price range filtering  
- **Store** - Product type (t-shirt, sweater, etc.)
- **Stock Status** - In stock, low stock, out of stock

**LLM-based filters (placeholders):**
- **Material** - Will be populated by AI analysis
- **Occasion** - Will be populated by AI analysis  
- **Season** - Will be populated by AI analysis

## Maintenance

### Regenerating Filter Data

When the product database changes, regenerate the static files:

```bash
# Using the npm script
npm run generate-filters

# Or directly
node scripts/generate-filters-map.js
```

This will:
1. Fetch all products from the FastAPI backend
2. Build the filters map with current data
3. Generate filter options for the UI
4. Save all files to the `data/` directory
5. Display a summary of the generated data

### Adding New Filter Types

To add a new filter type:

1. Update the `ProductAttributes` interface in `lib/product-filters.ts`
2. Modify the `buildFiltersMap()` function in `scripts/generate-f
[truncated — 1583 more characters]
```

### package.json

```
{
  "name": "my-v0-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "lint": "next lint",
    "start": "next start",
    "generate-filters": "node scripts/generate-filters-map.js"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-accordion": "latest",
    "@radix-ui/react-alert-dialog": "latest",
    "@radix-ui/react-aspect-ratio": "latest",
    "@radix-ui/react-avatar": "latest",
    "@radix-ui/react-checkbox": "latest",
    "@radix-ui/react-collapsible": "latest",
    "@radix-ui/react-context-menu": "latest",
    "@radix-ui/react-dialog": "latest",
    "@radix-ui/react-dropdown-menu": "latest",
    "@radix-ui/react-hover-card": "latest",
    "@radix-ui/react-label": "latest",
    "@radix-ui/react-menubar": "latest",
    "@radix-ui/react-navigation-menu": "latest",
    "@radix-ui/react-popover": "latest",
    "@radix-ui/react-progress": "latest",
    "@radix-ui/react-radio-group": "latest",
    "@radix-ui/react-scroll-area": "latest",
    "@radix-ui/react-select": "latest",
    "@radix-ui/react-separator": "latest",
    "@radix-ui/react-slider": "latest",
    "@radix-ui/react-slot": "latest",
    "@radix-ui/react-switch": "latest",
    "@radix-ui/react-tabs": "latest",
    "@radix-ui/react-toast": "latest",
    "@radix-ui/react-toggle": "latest",
    "@radix-ui/react-toggle-group": "latest",
    "@radix-ui/react-tooltip": "latest",
    "@vapi-ai/web": "^2.3.6",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "latest",
    "date-fns": "4.1.0",
    "embla-carousel-react": "latest",
    "input-otp": "latest",
    "lucide-react": "^0.454.0",
    "next": "15.2.4",
    "next-themes": "latest",
    "react": "^19",
    "react-day-picker": "latest",
    "react-dom": "^19",
    "react-hook-form": "latest",
    "react-resizable-panels": "latest",
    "recharts": "latest",
    "sonner": "latest",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "latest",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8.5",
    "tailwindcss": "^3.4.17",
    "typescript": "^5"
  }
}
```

### app/layout.tsx

```typescript
import type React from "react"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"
import BottomTabBar from "./components/BottomTabBar"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "StyleAI - Your AI Fashion Stylist",
  description: "Discover your style with AI-powered fashion recommendations",
    generator: 'v0.dev'
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} bg-black text-white overflow-x-hidden`}>
        <div className="min-h-screen max-w-[480px] mx-auto relative">
          {children}
          <BottomTabBar />
        </div>
      </body>
    </html>
  )
}

```

### app/page.tsx

```typescript
"use client"
import { useState, useRef, useEffect } from "react"
import { Mic, MicOff, Volume2, VolumeX, Camera, RotateCcw } from "lucide-react"
import AnimatedBackground from "./components/AnimatedBackground"
import VapiVoiceWidget from "../components/VapiVoiceWidget"
import TranscriptBox from "../components/TranscriptBox"
import { analyzeAndStoreSkinTone } from "../lib/skin-tone-storage"

interface VoiceFilters {
  color: string | null
  type: string | null
  priceMin: number | null
  priceMax: number | null
  store: string | null
  inStockMin: number | null
  material: string | null
  occasion: string | null
  season: string | null
}

export default function HomePage() {
  const [micEnabled, setMicEnabled] = useState(false)
  const [speakerEnabled, setSpeakerEnabled] = useState(true)
  const [cameraEnabled, setCameraEnabled] = useState(false)
  const [cameraError, setCameraError] = useState<string | null>(null)
  const [isFrontCamera, setIsFrontCamera] = useState(true)
  const [micStream, setMicStream] = useState<MediaStream | null>(null)
  
  // Voice agent states
  const [voiceTranscript, setVoiceTranscript] = useState<Array<{role: string, text: string}>>([])
  const [isVoiceConnected, setIsVoiceConnected] = useState(false)
  const [isVoiceSpeaking, setIsVoiceSpeaking] = useState(false)
  
  // Skin tone analysis states
  const [isAnalyzing, setIsAnalyzing] = useState(false)
  const [analysisMessage, setAnalysisMessage] = useState<string | null>(null)
  
  // Filter extraction state
  const [extractedFilters, setExtractedFilters] = useState<VoiceFilters | null>(null)
  
  const videoRef = useRef<HTMLVideoElement>(null)
  const streamRef = useRef<MediaStream | null>(null)

  const startCamera = async (facingMode: 'user' | 'environment' = 'user') => {
    try {
      setCameraError(null)
      
      // Stop existing stream if any
      if (streamRef.current) {
        streamRef.current.getTracks().forEach(track => track.stop())
        streamRef.current = null
      }

      // Stop video element first to prevent AbortError
      if (videoRef.current) {
        videoRef.current.srcObject = null
      }

      const constraints = {
        video: {
          facingMode: facingMode,
          width: { ideal: 1920 },
          height: { ideal: 1080 }
        },
        audio: false
      }

      const stream = await navigator.mediaDevices.getUserMedia(constraints)
      streamRef.current = stream
      
      if (videoRef.current) {
        videoRef.current.srcObject = stream
        // Wait for video to be ready before playing
        try {
          await videoRef.current.play()
        } catch (playError) {
          console.warn('Video play error (non-critical):', playError)
          // Don't throw error for play issues as they're often browser-specific
        }
      }
      
      setCameraEnabled(true)
    } catch (error) {
      console.error('Camera error:', error)
      setCameraError('Unable to access camera. Please check permissions.')
      setCameraEnabled(false)
    }
  }

  const stopCamera = () => {
    if (streamRef.current) {
      streamRef.current.getTracks().forEach(track => track.stop())
      streamRef.current = null
    }
    setCameraEnabled(false)
  }

  const switchCamera = () => {
    const newFacingMode = isFrontCamera ? 'environment' : 'user'
    setIsFrontCamera(!isFrontCamera)
    startCamera(newFacingMode)
  }

  const capturePhoto = async () => {
    if (!videoRef.current || !cameraEnabled) return
    
    try {
      setIsAnalyzing(true)
      setAnalysisMessage("Analyzing skin tone...")
      
      const canvas = document.createElement('canvas')
      const context = canvas.getContext('2d')
      
      if (context) {
        canvas.width = videoRef.current.videoWidth
        canvas.height = videoRef.current.videoHeight
        context.drawImage(videoRef.current, 0, 0)
        
        // Get image data as base64
        const imageDataUrl = canvas.toDataURL('image/jpeg', 0.8)
        
        // Analyze skin tone and store it
        await analyzeAndStoreSkinTone(imageDataUrl)
        
        setAnalysisMessage("Skin tone updated! ✨")
        
        // Clear message after 3 seconds
        setTimeout(() => {
          setAnalysisMessage(null)
        }, 3000)
      }
    } catch (error) {
      console.error('Skin tone analysis failed:', error)
      setAnalysisMessage("Analysis failed. Please try again.")
      
      // Clear error message after 3 seconds
      setTimeout(() => {
        setAnalysisMessage(null)
      }, 3000)
    } finally {
      setIsAnalyzing(false)
    }
  }

  const toggleMicrophone = async () => {
    if (micEnabled) {
      // Stop microphone
      if (micStream) {
        micStream.getTracks().forEach(track => track.stop())
        setMicStream(null)
      }
      setMicEnabled(false)
    } else {
      // Start microphone
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: {
            echoCancellation: true,
            noiseSuppression: true,
            autoGainControl: true
          }
        })
        setMicStream(stream)
        setMicEnabled(true)
      } catch (error) {
        console.error('Microphone error:', error)
        alert('Unable to access microphone. Please check permissions.')
      }
    }
  }

  useEffect(() => {
    // Start camera when component mounts
    startCamera()
    
    // Cleanup on unmount
    return () => {
      stopCamera()
      if (micStream) {
        micStream.getTracks().forEach(track => track.stop())
      }
    }
  }, [])

  // Handle filter extraction and navigation
  useEffect(() => {
    if (extractedFilters && Object.keys(extractedFilters).some(key => extractedFilters[key as keyof VoiceFilters] !== null)) {
      console.log('🏠 HomePage: Voice filters extracted, navigating to swipe page:', extractedFilters)
      
      // Store filters in localStorage for swipe page using our new format
      localStorage.setItem('ac
[truncated — 5955 more characters]
```

### app/color-analysis/page.tsx

```typescript
"use client"
import { useState } from "react"
import { ArrowRight, Sparkles } from "lucide-react"
import Link from "next/link"
import AnimatedBackground from "../components/AnimatedBackground"

export default function ColorAnalysisPage() {
  const [step, setStep] = useState(1)
  const [isAnalyzing, setIsAnalyzing] = useState(false)

  const handleAnalyze = () => {
    setIsAnalyzing(true)
    setTimeout(() => {
      setIsAnalyzing(false)
      setStep(3)
    }, 3000)
  }

  const colorPalette = ["#8B4513", "#D2691E", "#CD853F", "#DEB887", "#F4A460", "#DAA520", "#B8860B", "#9ACD32"]

  return (
    <div className="min-h-screen relative pb-20">
      <AnimatedBackground />

      <div className="relative z-10 p-6">
        <h1 className="text-3xl font-bold gradient-text text-center mb-8">Color Analysis</h1>

        {step === 1 && (
          <div className="space-y-6">
            {/* Face Alignment Guide */}
            <div className="glass-card rounded-3xl p-6 text-center">
              <div className="w-48 h-64 mx-auto mb-4 rounded-3xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 border-2 border-dashed border-purple-400 flex items-center justify-center">
                <div className="w-32 h-40 rounded-full border-2 border-purple-400 border-dashed flex items-center justify-center">
                  <span className="text-purple-400">👤</span>
                </div>
              </div>
              <h2 className="text-xl font-semibold mb-2">Position Your Face</h2>
              <p className="text-gray-300 mb-4">Align your face within the oval guide for accurate color analysis</p>
              <ul className="text-sm text-gray-400 space-y-1">
                <li>• Ensure good lighting</li>
                <li>• Remove makeup if possible</li>
                <li>• Look directly at camera</li>
              </ul>
            </div>

            <button
              onClick={() => setStep(2)}
              className="w-full bg-gradient-to-r from-purple-500 to-pink-500 text-white py-4 rounded-2xl font-semibold animate-pulse-glow flex items-center justify-center gap-2"
            >
              Start Analysis <ArrowRight size={20} />
            </button>
          </div>
        )}

        {step === 2 && (
          <div className="text-center">
            <button
              onClick={handleAnalyze}
              disabled={isAnalyzing}
              className="w-full glass-card rounded-3xl p-8 hover:bg-white/20 transition-all duration-300"
            >
              {isAnalyzing ? (
                <div className="space-y-4">
                  <div className="w-16 h-16 mx-auto rounded-full bg-gradient-to-r from-purple-500 to-pink-500 animate-spin flex items-center justify-center">
                    <Sparkles className="text-white" size={24} />
                  </div>
                  <h2 className="text-xl font-semibold gradient-text">Finding your color season...</h2>
                  <p className="text-gray-300">Analyzing skin tone, hair, and eyes</p>
                </div>
              ) : (
                <div className="space-y-4">
                  <div className="w-24 h-24 mx-auto rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center animate-pulse-glow">
                    <Sparkles className="text-white" size={32} />
                  </div>
                  <h2 className="text-xl font-semibold">Ready to Analyze</h2>
                  <p className="text-gray-300">Tap to discover your color season</p>
                </div>
              )}
            </button>
          </div>
        )}

        {step === 3 && (
          <div className="space-y-6">
            {/* Result Card */}
            <div className="glass-card rounded-3xl p-6 text-center">
              <div className="w-20 h-20 mx-auto mb-4 rounded-full bg-gradient-to-r from-orange-500 to-yellow-500 flex items-center justify-center animate-pulse-glow">
                <span className="text-2xl">🍂</span>
              </div>
              <h2 className="text-2xl font-bold gradient-text mb-2">Warm Autumn</h2>
              <p className="text-gray-300 mb-4">
                You have warm undertones with rich, earthy colors that complement your natural beauty
              </p>

              {/* Color Swatches */}
              <div className="flex justify-center gap-2 mb-4">
                {colorPalette.map((color, index) => (
                  <div
                    key={index}
                    className="w-8 h-8 rounded-full border-2 border-white/20"
                    style={{ backgroundColor: color }}
                  />
                ))}
              </div>

              <button className="w-full bg-white/10 hover:bg-white/20 text-white py-3 rounded-xl transition-all duration-300">
                View Full Report
              </button>
            </div>

            {/* CTA */}
            <Link
              href="/swipe"
              className="block w-full bg-gradient-to-r from-purple-500 to-pink-500 text-white py-4 rounded-2xl font-semibold text-center animate-pulse-glow"
            >
              Start Browsing Outfits ✨
            </Link>
          </div>
        )}
      </div>
    </div>
  )
}

```

### app/wrapped/page.tsx

```typescript
"use client"
import { useState } from "react"
import { ChevronDown, Share } from "lucide-react"
import AnimatedBackground from "../components/AnimatedBackground"

const slides = [
  {
    id: 1,
    title: "Your 2025 Style Wrapped",
    subtitle: "Let's see what you've been up to! 🎬",
    content: (
      <div className="text-center">
        <div className="w-32 h-32 mx-auto mb-6 rounded-full bg-gradient-to-r from-purple-500 via-pink-500 to-blue-500 animate-gradient flex items-center justify-center">
          <span className="text-4xl">✨</span>
        </div>
        <p className="text-gray-300">Ready to discover your fashion journey?</p>
      </div>
    ),
  },
  {
    id: 2,
    title: "Your Top 3 Brands",
    subtitle: "You've got great taste! 👑",
    content: (
      <div className="space-y-6">
        <div className="flex items-center gap-4 glass-card rounded-2xl p-4">
          <div className="w-12 h-12 rounded-full bg-gradient-to-r from-yellow-500 to-orange-500 flex items-center justify-center font-bold">
            1
          </div>
          <div>
            <h3 className="font-bold text-lg">Zara</h3>
            <p className="text-gray-400">47 items purchased</p>
          </div>
        </div>
        <div className="flex items-center gap-4 glass-card rounded-2xl p-4">
          <div className="w-12 h-12 rounded-full bg-gradient-to-r from-gray-400 to-gray-600 flex items-center justify-center font-bold">
            2
          </div>
          <div>
            <h3 className="font-bold text-lg">H&M</h3>
            <p className="text-gray-400">32 items purchased</p>
          </div>
        </div>
        <div className="flex items-center gap-4 glass-card rounded-2xl p-4">
          <div className="w-12 h-12 rounded-full bg-gradient-to-r from-orange-600 to-yellow-600 flex items-center justify-center font-bold">
            3
          </div>
          <div>
            <h3 className="font-bold text-lg">ASOS</h3>
            <p className="text-gray-400">28 items purchased</p>
          </div>
        </div>
      </div>
    ),
  },
  {
    id: 3,
    title: "Your Signature Color",
    subtitle: "You wore this color 73% of the time! 🎨",
    content: (
      <div className="text-center">
        <div className="w-40 h-40 mx-auto mb-6 rounded-full bg-gradient-to-br from-purple-600 to-indigo-600 animate-pulse-glow flex items-center justify-center">
          <span className="text-white text-2xl font-bold">Purple</span>
        </div>
        <p className="text-gray-300 mb-4">Your most-worn color family</p>
        <div className="flex justify-center gap-2">
          {["#8B5CF6", "#7C3AED", "#6D28D9", "#5B21B6"].map((color, index) => (
            <div
              key={index}
              className="w-8 h-8 rounded-full border-2 border-white/20"
              style={{ backgroundColor: color }}
            />
          ))}
        </div>
      </div>
    ),
  },
  {
    id: 4,
    title: "Your Style Stats",
    subtitle: "Look at you go! 📊",
    content: (
      <div className="grid grid-cols-2 gap-4">
        <div className="glass-card rounded-2xl p-4 text-center">
          <div className="text-3xl font-bold gradient-text mb-2">127</div>
          <p className="text-gray-400 text-sm">Outfits Tried</p>
        </div>
        <div className="glass-card rounded-2xl p-4 text-center">
          <div className="text-3xl font-bold gradient-text mb-2">89</div>
          <p className="text-gray-400 text-sm">AR Try-Ons</p>
        </div>
        <div className="glass-card rounded-2xl p-4 text-center">
          <div className="text-3xl font-bold gradient-text mb-2">23</div>
          <p className="text-gray-400 text-sm">Style Challenges</p>
        </div>
        <div className="glass-card rounded-2xl p-4 text-center">
          <div className="text-3xl font-bold gradient-text mb-2">156</div>
          <p className="text-gray-400 text-sm">Community Likes</p>
        </div>
      </div>
    ),
  },
  {
    id: 5,
    title: "Share Your Wrapped",
    subtitle: "Show off your style journey! 📱",
    content: (
      <div className="text-center">
        <div className="w-32 h-32 mx-auto mb-6 rounded-full bg-gradient-to-r from-green-500 to-blue-500 animate-pulse-glow flex items-center justify-center">
          <Share className="text-white" size={48} />
        </div>
        <p className="text-gray-300 mb-6">Ready to share your 2025 Style Wrapped?</p>
        <button className="w-full bg-gradient-to-r from-purple-500 to-pink-500 text-white py-4 rounded-2xl font-semibold animate-pulse-glow">
          Share to Stories
        </button>
      </div>
    ),
  },
]

export default function WrappedPage() {
  const [currentSlide, setCurrentSlide] = useState(0)

  const nextSlide = () => {
    if (currentSlide < slides.length - 1) {
      setCurrentSlide(currentSlide + 1)
    }
  }

  const prevSlide = () => {
    if (currentSlide > 0) {
      setCurrentSlide(currentSlide - 1)
    }
  }

  return (
    <div className="min-h-screen relative overflow-hidden">
      <AnimatedBackground />

      <div className="relative z-10 h-screen flex flex-col">
        {/* Slide Content */}
        <div className="flex-1 flex flex-col justify-center px-6">
          <div className="text-center mb-8">
            <h1 className="text-3xl font-bold gradient-text mb-2">{slides[currentSlide].title}</h1>
            <p className="text-gray-300">{slides[currentSlide].subtitle}</p>
          </div>

          <div className="flex-1 flex items-center justify-center">{slides[currentSlide].content}</div>
        </div>

        {/* Navigation */}
        <div className="p-6 pb-24">
          {/* Progress Dots */}
          <div className="flex justify-center gap-2 mb-6">
            {slides.map((_, index) => (
              <button
                key={index}
                onClick={() => setCurrentSlide(index)}
                className={`w-2 h-2 rounded-full transition-all duration-300 ${
                  ind
[truncated — 1309 more characters]
```

### app/community/page.tsx

```typescript
"use client"
import { useState } from "react"
import { Heart, MessageCircle, Share, Camera } from "lucide-react"
import AnimatedBackground from "../components/AnimatedBackground"

interface Post {
  id: number
  user: {
    name: string
    avatar: string
    username: string
  }
  image: string
  caption: string
  likes: number
  comments: number
  timestamp: string
  items: Array<{ name: string; brand: string; price: number }>
  reward: {
    views: number
    buys: number
    discount: number
    brand: string
  }
}

const posts: Post[] = [
  {
    id: 1,
    user: {
      name: "Emma Style",
      avatar: "/placeholder.svg?height=40&width=40",
      username: "@emmastyle",
    },
    image: "/placeholder.svg?height=400&width=300",
    caption: "Obsessed with this autumn vibe! 🍂✨",
    likes: 1247,
    comments: 89,
    timestamp: "2h ago",
    items: [
      { name: "Oversized Blazer", brand: "Zara", price: 89 },
      { name: "High-Waist Jeans", brand: "H&M", price: 45 },
    ],
    reward: {
      views: 3500,
      buys: 9,
      discount: 10,
      brand: "Zara",
    },
  },
  {
    id: 2,
    user: {
      name: "Alex Chen",
      avatar: "/placeholder.svg?height=40&width=40",
      username: "@alexchen",
    },
    image: "/placeholder.svg?height=400&width=300",
    caption: "Street style vibes for the weekend 🔥",
    likes: 892,
    comments: 45,
    timestamp: "5h ago",
    items: [
      { name: "Graphic Tee", brand: "Urban Outfitters", price: 35 },
      { name: "Cargo Pants", brand: "ASOS", price: 65 },
    ],
    reward: {
      views: 2100,
      buys: 5,
      discount: 8,
      brand: "ASOS",
    },
  },
]

export default function CommunityPage() {
  const [showCreatePost, setShowCreatePost] = useState(false)
  const [likedPosts, setLikedPosts] = useState<Set<number>>(new Set())

  const handleLike = (postId: number) => {
    const newLikedPosts = new Set(likedPosts)
    if (newLikedPosts.has(postId)) {
      newLikedPosts.delete(postId)
    } else {
      newLikedPosts.add(postId)
    }
    setLikedPosts(newLikedPosts)
  }

  return (
    <div className="min-h-screen relative pb-20">
      <AnimatedBackground />

      <div className="relative z-10">
        {/* Header */}
        <div className="p-6 pb-0">
          <h1 className="text-2xl font-bold gradient-text text-center">Community</h1>
        </div>

        {/* Posts Feed */}
        <div className="space-y-6 p-6">
          {posts.map((post) => (
            <div key={post.id} className="glass-card rounded-3xl overflow-hidden">
              {/* Reward Banner */}
              <div className="bg-gradient-to-r from-green-500 to-blue-500 p-3 text-center">
                <p className="text-white text-sm font-semibold">
                  🎉 You earned {post.reward.discount}% off at {post.reward.brand} from{" "}
                  {post.reward.views.toLocaleString()} views and {post.reward.buys} buys!
                </p>
              </div>

              {/* User Header */}
              <div className="flex items-center gap-3 p-4 pb-2">
                <img
                  src={post.user.avatar || "/placeholder.svg"}
                  alt={post.user.name}
                  className="w-10 h-10 rounded-full"
                />
                <div className="flex-1">
                  <h3 className="font-semibold">{post.user.name}</h3>
                  <p className="text-gray-400 text-sm">
                    {post.user.username} • {post.timestamp}
                  </p>
                </div>
              </div>

              {/* Post Image */}
              <div className="relative">
                <img src={post.image || "/placeholder.svg"} alt="Outfit post" className="w-full h-80 object-cover" />

                {/* Product Tags */}
                <div className="absolute bottom-4 left-4 space-y-2">
                  {post.items.map((item, index) => (
                    <button
                      key={index}
                      className="block glass-card rounded-full px-3 py-1 text-sm text-white hover:bg-white/20 transition-all duration-300"
                    >
                      {item.brand} - ${item.price}
                    </button>
                  ))}
                </div>
              </div>

              {/* Post Content */}
              <div className="p-4">
                <p className="mb-3">{post.caption}</p>

                {/* Actions */}
                <div className="flex items-center gap-6">
                  <button
                    onClick={() => handleLike(post.id)}
                    className={`flex items-center gap-2 transition-all duration-300 ${
                      likedPosts.has(post.id) ? "text-red-500" : "text-gray-400 hover:text-red-500"
                    }`}
                  >
                    <Heart size={20} fill={likedPosts.has(post.id) ? "currentColor" : "none"} />
                    <span>{post.likes + (likedPosts.has(post.id) ? 1 : 0)}</span>
                  </button>

                  <button className="flex items-center gap-2 text-gray-400 hover:text-white transition-all duration-300">
                    <MessageCircle size={20} />
                    <span>{post.comments}</span>
                  </button>

                  <button className="flex items-center gap-2 text-gray-400 hover:text-white transition-all duration-300">
                    <Share size={20} />
                  </button>
                </div>
              </div>
            </div>
          ))}
        </div>

        {/* Disclaimer */}
        <div className="px-6 pb-6">
          <div className="glass-card rounded-2xl p-4 border border-yellow-500/30">
            <p className="text-yellow-400 text-sm">
              ⚠️ Rewards shown are illustrative. Actual discounts depend on verified performance and participating
              brands.
            </p>
          </div>
        </div>
      </div>

      {/* Create Post FAB */}
      <button
    
[truncated — 2351 more characters]
```

### app/api/try-on/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { model_image, garment_image, category = 'auto', mode = 'balanced' } = body

    if (!model_image || !garment_image) {
      return NextResponse.json(
        { error: 'model_image and garment_image are required' },
        { status: 400 }
      )
    }

    const fashnApiKey = process.env.FASHN_API_KEY
    if (!fashnApiKey) {
      return NextResponse.json(
        { error: 'FASHN API key not configured' },
        { status: 500 }
      )
    }

    // Call FASHN API
    const fashnResponse = await fetch('https://api.fashn.ai/v1/run', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${fashnApiKey}`
      },
      body: JSON.stringify({
        model_image,
        garment_image,
        category,
        mode,
        output_format: 'jpeg',
        return_base64: false,
        num_samples: 1
      })
    })

    if (!fashnResponse.ok) {
      const errorText = await fashnResponse.text()
      console.error('FASHN API error:', errorText)
      return NextResponse.json(
        { error: 'Failed to start try-on process' },
        { status: fashnResponse.status }
      )
    }

    const data = await fashnResponse.json()
    return NextResponse.json(data)

  } catch (error) {
    console.error('Try-on API error:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
} 
```

### app/api/try-on/[id]/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  try {
    const { id } = await params

    if (!id) {
      return NextResponse.json(
        { error: 'Prediction ID is required' },
        { status: 400 }
      )
    }

    const fashnApiKey = process.env.FASHN_API_KEY
    if (!fashnApiKey) {
      return NextResponse.json(
        { error: 'FASHN API key not configured' },
        { status: 500 }
      )
    }

    // Check status with FASHN API
    const fashnResponse = await fetch(`https://api.fashn.ai/v1/status/${id}`, {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${fashnApiKey}`
      }
    })

    if (!fashnResponse.ok) {
      const errorText = await fashnResponse.text()
      console.error('FASHN API status error:', errorText)
      return NextResponse.json(
        { error: 'Failed to check try-on status' },
        { status: fashnResponse.status }
      )
    }

    const data = await fashnResponse.json()
    return NextResponse.json(data)

  } catch (error) {
    console.error('Try-on status API error:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
} 
```

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