# Project export: DeepDrive

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 2026
- Tagline: Car buying made modern. Real listings. AI-powered breakdowns. Know everything about a car before you buy - specs, reliability, price rating, vehicle history, and more. No need for extra tabs.
- Devpost: https://devpost.com/software/deepdrive-0y8k6x
- GitHub: https://github.com/zdanger1905/deepdrive
- Video: https://www.youtube.com/embed/8Msi-hPVTC8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Claude Sonnet 4.6 (1 commits), Zayd (1 commits)

## Devpost submission (written by the team)

### Inspiration

Inspiration I was helping my younger brother shop for his first car. He had like 12 tabs open: CarGurus for listings, Edmunds for reviews, YouTube for reliability videos, Reddit for owner opinions, and he still wasn't sure if the price was good. I thought: why does buying a car require this much homework? That frustration turned into DeepDrive.

### What it does

DeepDrive pulls live car listings and instantly generates a full AI breakdown for any car you're looking at: performance specs, reliability history, trim comparisons, available packages, and an estimated deal rating. There's also an in-page chat so you can ask anything about that specific car without leaving. And MyDrive lets you describe what you want in plain English ("sporty but practical, 5 seats, under $40k") and the AI finds real matching listings available right now. How I built it Next.js 16 (App Router) + TypeScript + Tailwind CSS v4 Auto.dev API for live car listings Claude (claude-sonnet-4-6) for AI breakdowns, chat, and the MyDrive car matcher Upstash Redis for caching breakdowns (24h TTL) and session memory Challenges I ran into The biggest one was Redis. I started with Redis Cloud over TCP and kept hitting ECONNRESET errors in the Next.js serverless environment. After hours of debugging TLS configurations I switched to Upstash, which uses HTTP/REST, problem instantly gone. Sometimes the right move is just changing tools. Another challenge was the MyDrive feature. Auto.dev doesn't expose things like 0-60 times or "luxury level" as searchable fields. The workaround: Claude interprets the user's preferences and recommends specific makes and models, then we query the index for those exact cars. If they're not in the index, they don't show up. Accomplishments that I'm proud of Getting the AI breakdown to feel genuinely useful rather than like a gimmick is what I'm most proud of. The trim ladder, package comparison, and deal ratings together give a buyer real information they'd otherwise spend an hour across multiple sites trying to find. MyDrive also came out better than expected — describing what you want in plain English and getting back real listings with AI reasoning for each recommendation feels like a fundamentally different way to shop for a car. Most platforms assume you already know the make and model you want. I'm also proud of the overall design. The UI is dark, clean, and intentional, and the AI chat has a custom aesthetic with gradient borders and ambient glow that actually feels like an AI product rather than a widget bolted on. What I learned Mostly that AI is most useful when it's doing something the user genuinely can't do themselves — not just summarizing, but synthesizing across specs, pricing, reliability data, and market context all at once. Building DeepDrive made me think a lot about where AI adds real value versus where it's just a gimmick.

### What's next

The immediate priorities are user accounts so search history and liked cars persist across devices, price drop tracking with notifications, and a financing calculator that shows real monthly payments per listing based on down payment and loan term. Longer term I want to add a side-by-side comparison mode where you can pit two or three listings against each other and get an AI breakdown of the differences, real deal ratings through actual CarGurus and Edmunds data partnerships, and a mobile app where you can scan a VIN at a dealership and get an instant breakdown on the spot.

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 28 recognized source files, 123 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Python (language) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (33 of 33)

```
.gitignore
AGENTS.md
app/api/ai-chat/route.ts
app/api/breakdown/route.ts
app/api/chat/route.ts
app/api/listings/[vin]/route.ts
app/api/listings/route.ts
app/api/mydrive/route.ts
app/deepdrive-ai/page.tsx
app/globals.css
app/layout.tsx
app/liked/page.tsx
app/listing/[vin]/page.tsx
app/mydrive/page.tsx
app/page.tsx
CLAUDE.md
components/Footer.tsx
components/ListingCard.tsx
components/Navbar.tsx
components/SearchForm.tsx
eslint.config.mjs
hooks/useLikes.ts
lib/ai.ts
lib/autodev.ts
lib/breakdown-cache.ts
lib/car-vectors.ts
lib/redis.ts
lib/session-memory.ts
next.config.ts
package.json
postcss.config.mjs
README.md
tsconfig.json
```

### Dependencies

- package.json: @anthropic-ai/sdk@^0.105.0, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, @upstash/redis@^1.38.0, eslint@^9, eslint-config-next@16.2.9, next@16.2.9, react@19.2.4, react-dom@19.2.4, redis@^6.0.0, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- Initial commit — DeepDrive

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

### CLAUDE.md

```markdown
@AGENTS.md

```

### AGENTS.md

```markdown
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->

```

### package.json

```
{
  "name": "carstuff",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.105.0",
    "@upstash/redis": "^1.38.0",
    "next": "16.2.9",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "redis": "^6.0.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";

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

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

export const metadata: Metadata = {
  title: "DeepDrive — Find Your Next Car",
  description: "Active car listings with AI-powered breakdowns. No extra tabs needed.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
      <body className="min-h-screen bg-zinc-950 text-zinc-100 flex flex-col">
        <Navbar />
        {children}
        <Footer />
      </body>
    </html>
  );
}

```

### app/page.tsx

```typescript
'use client'

import { useState, useEffect, useCallback, Suspense } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import SearchForm, { SearchFilters } from '@/components/SearchForm'
import ListingCard from '@/components/ListingCard'
import type { Listing } from '@/lib/autodev'


function SearchPage() {
  const router = useRouter()
  const searchParams = useSearchParams()
  const [listings, setListings] = useState<Listing[]>([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [sponsored, setSponsored] = useState<Listing[]>([])

  useEffect(() => {
    fetch('/api/listings?limit=8')
      .then(r => r.json())
      .then((res: { data?: Listing[] }) => setSponsored(res.data ?? []))
      .catch(() => {})
  }, [])

  const filtersFromUrl: SearchFilters = {
    make: searchParams.get('make') ?? '',
    model: searchParams.get('model') ?? '',
    zip: searchParams.get('zip') ?? '',
    distance: searchParams.get('distance') ?? '50',
    priceMin: searchParams.get('priceMin') ?? '',
    priceMax: searchParams.get('priceMax') ?? '',
    yearMin: searchParams.get('yearMin') ?? '',
    yearMax: searchParams.get('yearMax') ?? '',
    bodyStyle: searchParams.get('bodyStyle') ?? '',
    drivetrain: searchParams.get('drivetrain') ?? '',
    fuelType: searchParams.get('fuelType') ?? '',
    transmission: searchParams.get('transmission') ?? '',
    maxMileage: searchParams.get('maxMileage') ?? '',
    dealRating: searchParams.get('dealRating') ?? '',
    cpoOnly: searchParams.get('cpoOnly') ?? '',
    noAccidents: searchParams.get('noAccidents') ?? '',
    singleOwner: searchParams.get('singleOwner') ?? '',
  }

  const hasActiveSearch = searchParams.has('make') || searchParams.has('model') || searchParams.has('zip')

  function applyClientFilters(data: Listing[]): Listing[] {
    const maxMileage = searchParams.get('maxMileage')
    const dealRating = searchParams.get('dealRating')
    const noAccidents = searchParams.get('noAccidents') === 'true'
    const singleOwner = searchParams.get('singleOwner') === 'true'

    return data.filter(l => {
      if (maxMileage && (l.retailListing?.miles ?? Infinity) > Number(maxMileage)) return false
      if (noAccidents && l.history?.accidents) return false
      if (singleOwner && l.history && l.history.ownerCount > 1) return false
      if (dealRating) {
        const price = l.retailListing?.price
        const msrp = l.vehicle.baseMsrp
        if (price && msrp) {
          const pct = (msrp - price) / msrp
          if (dealRating === 'great' && pct < 0.20) return false
          if (dealRating === 'good' && pct < 0.10) return false
          if (dealRating === 'fair' && price > msrp) return false
        }
      }
      return true
    })
  }

  const runSearch = useCallback(async (params: URLSearchParams) => {
    setLoading(true)
    setError(null)
    try {
      const res = await fetch(`/api/listings?${params}`)
      const json = await res.json()
      if (!res.ok) throw new Error(json.error ?? 'Search failed')
      setListings(json.data ?? [])
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Something went wrong')
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => {
    if (!hasActiveSearch) return
    const params = new URLSearchParams()
    searchParams.forEach((v, k) => params.set(k, v))
    runSearch(params)
  }, [searchParams, hasActiveSearch, runSearch])

  function handleSearch(filters: SearchFilters) {
    const params = new URLSearchParams()
    if (filters.make) params.set('make', filters.make)
    if (filters.model) params.set('model', filters.model)
    if (filters.zip) params.set('zip', filters.zip)
    if (filters.distance) params.set('distance', filters.distance)
    if (filters.priceMin) params.set('priceMin', filters.priceMin)
    if (filters.priceMax) params.set('priceMax', filters.priceMax)
    if (filters.yearMin) params.set('yearMin', filters.yearMin)
    if (filters.yearMax) params.set('yearMax', filters.yearMax)
    if (filters.bodyStyle) params.set('bodyStyle', filters.bodyStyle)
    if (filters.drivetrain) params.set('drivetrain', filters.drivetrain)
    if (filters.fuelType) params.set('fuelType', filters.fuelType)
    if (filters.transmission) params.set('transmission', filters.transmission)
    if (filters.maxMileage) params.set('maxMileage', filters.maxMileage)
    if (filters.dealRating) params.set('dealRating', filters.dealRating)
    if (filters.cpoOnly) params.set('cpoOnly', filters.cpoOnly)
    if (filters.noAccidents) params.set('noAccidents', filters.noAccidents)
    if (filters.singleOwner) params.set('singleOwner', filters.singleOwner)
    router.push(`/?${params}`)
  }

  const filteredListings = applyClientFilters(listings)

  return (
    <main className="flex-1 bg-zinc-950">
      {/* Hero */}
      {!hasActiveSearch && (
        <div className="relative overflow-hidden">
          <div className="absolute inset-0 bg-gradient-to-br from-indigo-950/60 via-zinc-950 to-zinc-950 pointer-events-none" />
          <div className="absolute -top-32 -left-32 w-96 h-96 bg-indigo-600/10 rounded-full blur-3xl pointer-events-none" />
          <div className="absolute -top-16 right-0 w-72 h-72 bg-violet-600/10 rounded-full blur-3xl pointer-events-none" />
          <div className="relative max-w-7xl mx-auto px-4 pt-16 pb-10">
            <div className="max-w-2xl">
<h1 className="text-4xl sm:text-5xl font-bold text-zinc-100 leading-tight tracking-tight">
                Find your next car.<br />
                <span className="text-indigo-400">Know everything about it.</span>
              </h1>
              <p className="text-zinc-400 mt-4 text-lg">
                Active listings with instant AI breakdowns — specs, trim comparisons, reliability, value verdict. No extra tabs.
              </p>
            </div>
          </div>
        </div>
      )}

      <div className="max-w-7xl 
[truncated — 3124 more characters]
```

### app/liked/page.tsx

```typescript
'use client'

import { useRouter } from 'next/navigation'
import { useLikes } from '@/hooks/useLikes'

export default function LikedPage() {
  const { likedList, toggle } = useLikes()
  const router = useRouter()

  return (
    <main className="flex-1 bg-zinc-950 min-h-screen">
      <div className="max-w-5xl mx-auto px-4 py-10">
        <div className="flex items-center gap-3 mb-8">
          <button
            onClick={() => router.back()}
            className="text-zinc-500 hover:text-zinc-300 transition-colors text-sm"
          >
            ← Back
          </button>
          <h1 className="text-2xl font-bold text-zinc-100">Liked Cars</h1>
          {likedList.length > 0 && (
            <span className="bg-red-600/20 text-red-400 border border-red-500/30 text-xs font-bold px-2 py-0.5 rounded-full">
              {likedList.length}
            </span>
          )}
        </div>

        {likedList.length === 0 ? (
          <div className="flex flex-col items-center justify-center py-24 text-center">
            <span className="text-6xl mb-4 text-zinc-700">♡</span>
            <p className="text-zinc-400 text-lg font-medium">No liked cars yet</p>
            <p className="text-zinc-600 text-sm mt-1 mb-6">Tap the heart on any listing to save it here.</p>
            <button
              onClick={() => router.push('/')}
              className="bg-indigo-600 hover:bg-indigo-500 text-white font-semibold px-5 py-2.5 rounded-lg text-sm transition-colors"
            >
              Browse Listings
            </button>
          </div>
        ) : (
          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
            {likedList.map(car => (
              <div
                key={car.vin}
                className="bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden hover:border-indigo-500/50 hover:shadow-xl hover:shadow-indigo-900/20 transition-all duration-200 group"
              >
                <button
                  className="block w-full text-left"
                  onClick={() => router.push(`/listing/${car.vin}`)}
                >
                  <div className="relative h-44 bg-zinc-800 overflow-hidden">
                    {car.image ? (
                      <img
                        src={car.image}
                        alt={`${car.year} ${car.make} ${car.model}`}
                        className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
                      />
                    ) : (
                      <div className="flex items-center justify-center h-full text-zinc-600 text-sm">No photo</div>
                    )}
                    <div className="absolute inset-0 bg-gradient-to-t from-zinc-900/80 via-transparent to-transparent" />
                    {car.price && (
                      <p className="absolute bottom-2.5 right-2.5 text-white font-bold text-lg leading-none drop-shadow-lg">
                        ${car.price.toLocaleString()}
                      </p>
                    )}
                  </div>
                  <div className="p-4">
                    <p className="font-semibold text-zinc-100 leading-tight">{car.year} {car.make} {car.model}</p>
                    <p className="text-sm text-zinc-400 mt-0.5">{car.trim}</p>
                  </div>
                </button>
                <div className="px-4 pb-4">
                  <button
                    onClick={() => toggle(car)}
                    className="w-full flex items-center justify-center gap-2 py-2 rounded-lg border border-zinc-700 text-xs text-zinc-500 hover:border-red-500/50 hover:text-red-400 transition-colors"
                  >
                    ♥ Remove
                  </button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </main>
  )
}

```

### app/deepdrive-ai/page.tsx

```typescript
'use client'

import { useState, useRef, useEffect } from 'react'

interface Message {
  role: 'user' | 'assistant'
  content: string
}

const SUGGESTIONS = [
  'What SUVs have the best reliability under $35k?',
  'Is now a good time to buy or should I wait?',
  'How do I negotiate a car price at a dealership?',
  'What should I inspect on a used car before buying?',
  'Compare BMW 3 Series vs Audi A4',
  'What are the hidden costs of car ownership?',
]

function renderMarkdown(text: string) {
  const lines = text.split('\n')
  return lines.map((line, i) => {
    const bold = line.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
    if (line.startsWith('- ') || line.startsWith('• ')) {
      return <li key={i} className="ml-4 list-disc" dangerouslySetInnerHTML={{ __html: bold.replace(/^[-•]\s/, '') }} />
    }
    if (bold.trim() === '') return <br key={i} />
    return <p key={i} dangerouslySetInnerHTML={{ __html: bold }} />
  })
}

export default function DeepDriveAI() {
  const [messages, setMessages] = useState<Message[]>([])
  const [input, setInput] = useState('')
  const [loading, setLoading] = useState(false)
  const bottomRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (messages.length > 0) bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
  }, [messages])

  async function send() {
    const text = input.trim()
    if (!text || loading) return
    const newMessages: Message[] = [...messages, { role: 'user', content: text }]
    setMessages(newMessages)
    setInput('')
    setLoading(true)
    try {
      const res = await fetch('/api/ai-chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ messages: newMessages }),
      })
      const data = await res.json()
      setMessages(prev => [...prev, { role: 'assistant', content: data.reply ?? 'Something went wrong.' }])
    } catch {
      setMessages(prev => [...prev, { role: 'assistant', content: 'Something went wrong. Please try again.' }])
    } finally {
      setLoading(false)
    }
  }

  return (
    <main className="flex-1 bg-zinc-950 flex flex-col">
      {/* Header */}
      <div className="relative border-b border-zinc-800/40 overflow-hidden">
        <div className="absolute inset-0 bg-gradient-to-br from-indigo-950/50 via-zinc-950 to-zinc-950 pointer-events-none" />
        <div className="absolute -top-20 left-1/3 w-80 h-80 bg-indigo-600/10 rounded-full blur-3xl pointer-events-none" />
        <div className="relative max-w-3xl mx-auto px-6 pt-10 pb-8 text-center">
          <h1 className="text-3xl sm:text-4xl font-bold tracking-tight">
            <span className="text-zinc-100">Deep</span><span className="text-indigo-400">Drive</span><span className="text-zinc-100"> AI</span>
          </h1>
          <p className="mt-2 text-zinc-500 text-sm">Your expert automotive advisor. Ask anything about buying, comparing, or owning a car.</p>
        </div>
      </div>

      {/* Chat area */}
      <div className="flex-1 max-w-3xl w-full mx-auto px-4 py-6 flex flex-col">
        <div className="flex-1 space-y-4 mb-4 min-h-[200px]">
          {messages.length === 0 && (
            <div>
              <p className="text-xs text-zinc-600 text-center mb-4">Try asking...</p>
              <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
                {SUGGESTIONS.map(s => (
                  <button
                    key={s}
                    onClick={() => setInput(s)}
                    className="text-left text-sm border border-zinc-800 bg-zinc-900/50 hover:bg-zinc-800/70 hover:border-zinc-700 text-zinc-400 hover:text-zinc-200 px-4 py-3 rounded-xl transition-all"
                  >
                    {s}
                  </button>
                ))}
              </div>
            </div>
          )}

          {messages.map((m, i) => (
            <div key={i} className={`flex items-end gap-2 ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
              {m.role === 'assistant' && (
                <div className="w-7 h-7 rounded-full bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center shrink-0 mb-0.5 shadow shadow-indigo-900/50">
                  <svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" strokeWidth={2.5} viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" /></svg>
                </div>
              )}
              <div className={`max-w-[80%] px-4 py-3 rounded-2xl text-sm leading-relaxed space-y-1 ${
                m.role === 'user'
                  ? 'bg-indigo-600/80 text-white rounded-br-sm border border-indigo-500/30'
                  : 'bg-zinc-900 text-zinc-200 rounded-bl-sm border border-zinc-800'
              }`}>
                {m.role === 'user' ? m.content : renderMarkdown(m.content)}
              </div>
            </div>
          ))}

          {loading && (
            <div className="flex items-end gap-2">
              <div className="w-7 h-7 rounded-full bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center shrink-0 shadow shadow-indigo-900/50">
                <svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" strokeWidth={2.5} viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" /></svg>
              </div>
              <div className="bg-zinc-900 border border-zinc-800 px-4 py-3 rounded-2xl rounded-bl-sm flex gap-1.5 items-center">
                <span className="w-1.5 h-1.5 bg-indigo-400 rounded-full animate-bounce [animation-delay:0ms]" />
                <span
[truncated — 1711 more characters]
```

### app/mydrive/page.tsx

```typescript
'use client'

import { useState, useRef } from 'react'
import { useRouter } from 'next/navigation'
import ListingCard from '@/components/ListingCard'
import type { Listing } from '@/lib/autodev'

interface Recommendation {
  make: string
  model: string
  yearMin: number
  yearMax: number
  reasoning: string
  listings: Listing[]
}

const BUDGETS = ['Any', 'Under $15k', '$15k–$25k', '$25k–$40k', '$40k–$60k', '$60k–$80k', '$80k+']
const SEATS = ['Any', '2+', '4+', '5+', '7+']
const ACCELS = [
  { label: 'Any', value: 'any' },
  { label: 'Supercar (<4s)', value: 'under 4 seconds 0-60' },
  { label: 'Fast (4–6s)', value: '4 to 6 seconds 0-60' },
  { label: 'Average (6–8s)', value: '6 to 8 seconds 0-60' },
  { label: "Don't care", value: 'any' },
]
const BODY_STYLES = ['Any', 'Sedan', 'SUV', 'Coupe', 'Truck', 'Convertible', 'Van', 'Wagon', 'Hatchback']

function StarRating({ label, value, onChange }: { label: string; value: number; onChange: (v: number) => void }) {
  const [hovered, setHovered] = useState(0)
  return (
    <div>
      <p className="text-xs text-zinc-500 mb-1.5">{label}</p>
      <div className="flex gap-1">
        {[1, 2, 3, 4, 5].map(n => (
          <button
            key={n}
            type="button"
            onMouseEnter={() => setHovered(n)}
            onMouseLeave={() => setHovered(0)}
            onClick={() => onChange(value === n ? 0 : n)}
            className={`w-7 h-7 rounded-lg border text-sm transition-all ${
              n <= (hovered || value)
                ? 'bg-indigo-500/30 border-indigo-400/60 text-indigo-300'
                : 'bg-zinc-800/50 border-zinc-700/50 text-zinc-600'
            }`}
          >
            {n}
          </button>
        ))}
      </div>
    </div>
  )
}

export default function MyDrivePage() {
  const router = useRouter()
  const [description, setDescription] = useState('')
  const [budget, setBudget] = useState('Any')
  const [seats, setSeats] = useState('Any')
  const [acceleration, setAcceleration] = useState('any')
  const [sport, setSport] = useState(0)
  const [luxury, setLuxury] = useState(0)
  const [bodyStyle, setBodyStyle] = useState('Any')
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [results, setResults] = useState<Recommendation[] | null>(null)
  const resultsRef = useRef<HTMLDivElement>(null)
  const textareaRef = useRef<HTMLTextAreaElement>(null)

  async function handleFind() {
    setLoading(true)
    setError(null)
    setResults(null)
    try {
      const res = await fetch('/api/mydrive', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          description,
          budget: budget !== 'Any' ? budget : undefined,
          seats: seats !== 'Any' ? seats : undefined,
          acceleration: acceleration !== 'any' ? acceleration : undefined,
          sport: sport || undefined,
          luxury: luxury || undefined,
          bodyStyle: bodyStyle !== 'Any' ? bodyStyle : undefined,
        }),
      })
      const data = await res.json()
      if (!res.ok) throw new Error(data.error ?? 'Something went wrong')
      setResults(data.recommendations)
      setTimeout(() => resultsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 100)
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Something went wrong')
    } finally {
      setLoading(false)
    }
  }

  const hasInput = description.trim() || budget !== 'Any' || seats !== 'Any' || acceleration !== 'any' || sport || luxury || bodyStyle !== 'Any'

  return (
    <main className="flex-1 bg-zinc-950">
      {/* Hero */}
      <div className="relative overflow-hidden border-b border-zinc-800/40">
        <div className="absolute inset-0 bg-gradient-to-br from-indigo-950/50 via-zinc-950 to-zinc-950 pointer-events-none" />
        <div className="absolute -top-32 left-1/4 w-96 h-96 bg-indigo-600/10 rounded-full blur-3xl pointer-events-none" />
        <div className="absolute top-0 right-0 w-72 h-72 bg-violet-600/8 rounded-full blur-3xl pointer-events-none" />
        <div className="relative max-w-3xl mx-auto px-6 pt-14 pb-12 text-center">
          <h1 className="text-4xl sm:text-5xl font-bold text-zinc-100 tracking-tight">
            Tell us what you want.<br />
            <span className="text-indigo-400">We'll find the car.</span>
          </h1>
          <p className="mt-4 text-zinc-400 text-lg max-w-xl mx-auto">
            Describe your ideal car in plain English. MyDrive uses AI to match your preferences to real listings available right now.
          </p>
        </div>
      </div>

      <div className="max-w-3xl mx-auto px-6 py-10 space-y-8">
        {/* Description */}
        <div>
          <label className="block text-sm font-medium text-zinc-300 mb-2">Describe your ideal car</label>
          <textarea
            ref={textareaRef}
            value={description}
            onChange={e => setDescription(e.target.value)}
            placeholder="e.g. I want something sporty but practical enough for weekend road trips. I have two kids so I need at least 5 seats. I care more about handling than outright speed."
            rows={3}
            className="w-full bg-zinc-900 border border-zinc-700/60 rounded-xl px-4 py-3 text-sm text-zinc-100 placeholder-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all resize-none"
          />
        </div>

        {/* Preferences grid */}
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
          {/* Budget */}
          <div>
            <label className="block text-xs text-zinc-500 mb-1.5">Budget</label>
            <div className="flex flex-wrap gap-1.5">
              {BUDGETS.map(b => (
                <button key={b} type="button" onClick={() => setBudget(b)}
                  className={`text-xs px-3 py-1.5 rounded-lg border transition-all ${budget === b ?
[truncated — 5500 more characters]
```

### app/api/ai-chat/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })

interface ChatMessage {
  role: 'user' | 'assistant'
  content: string
}

export async function POST(req: NextRequest) {
  try {
    const { messages }: { messages: ChatMessage[] } = await req.json()
    if (!messages?.length) return NextResponse.json({ error: 'Missing messages' }, { status: 400 })

    const system = `You are DeepDrive AI, an expert automotive advisor. Help users with any car-related questions — buying advice, comparisons, reliability, ownership costs, specs, financing, negotiation tips, and more. Be direct, honest, and practical. Keep answers concise (3-5 sentences) unless the question needs more detail.`

    const response = await client.messages.create({
      model: 'claude-sonnet-4-6',
      max_tokens: 768,
      system,
      messages,
    })

    const text = response.content[0].type === 'text' ? response.content[0].text : ''
    return NextResponse.json({ reply: text })
  } catch (e) {
    const msg = e instanceof Error ? e.message : 'Unknown error'
    return NextResponse.json({ error: msg }, { status: 500 })
  }
}

```

### app/api/listings/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'
import { searchListings } from '@/lib/autodev'

export async function GET(req: NextRequest) {
  const s = req.nextUrl.searchParams
  try {
    const result = await searchListings({
      make: s.get('make') || undefined,
      model: s.get('model') || undefined,
      zip: s.get('zip') || undefined,
      distance: s.get('distance') ? Number(s.get('distance')) : 50,
      priceMin: s.get('priceMin') ? Number(s.get('priceMin')) : undefined,
      priceMax: s.get('priceMax') ? Number(s.get('priceMax')) : undefined,
      yearMin: s.get('yearMin') ? Number(s.get('yearMin')) : undefined,
      yearMax: s.get('yearMax') ? Number(s.get('yearMax')) : undefined,
      bodyStyle: s.get('bodyStyle') || undefined,
      drivetrain: s.get('drivetrain') || undefined,
      fuelType: s.get('fuelType') || undefined,
      transmission: s.get('transmission') || undefined,
      cpoOnly: s.get('cpoOnly') === 'true',
      page: s.get('page') ? Number(s.get('page')) : 1,
      limit: 50,
    })
    return NextResponse.json(result)
  } catch (e) {
    const msg = e instanceof Error ? e.message : 'Unknown error'
    return NextResponse.json({ error: msg }, { status: 500 })
  }
}

```

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