# Project export: SlugRoute

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

## Project metadata

- Hackathon: CruzHacks 2026
- Tagline: SlugRoute is smart campus navigation. We use live weather data to route around bad weather, while connecting you with friends and AI-curated locations nearby.
- Devpost: https://devpost.com/software/slugroute
- GitHub: https://github.com/karanideepak691/slugroute
- Video: https://www.youtube.com/embed/BY2-UIVe2x8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([MLH] Best Use of ElevenLabs; [Sponsor - Windborne] Best Use of Windborne API)
- Team: 1 GitHub contributor(s) — Deepak-karani (8 commits)

## Devpost submission (written by the team)

### Inspiration

UC Santa Cruz is famous for its beautiful but unpredictable terrain. A sunny afternoon at Science Hill can quickly become a foggy, wind-swept walk by the time you reach Porter. We noticed that traditional navigation apps are climate-blind. They optimize only for distance and time, not for comfort, safety, or environmental conditions. SlugRoute was created to give students a weather-intelligent navigation companion that understands campus microclimates the same way Slugs do.

### What it does

SlugRoute is an AI-powered campus navigation system that redesigns how students move through campus by integrating real-time atmospheric data. Instead of selecting routes based purely on distance, SlugRoute evaluates environmental exposure, physical effort, and weather comfort to recommend smarter paths. Users can prioritize faster travel, lower wind exposure, or more comfortable walking conditions depending on their needs.

### How we built it

We focused on a performance-first architecture to maintain smooth real-time responsiveness. The frontend was built using Next.js 16 with Turbopack for fast builds and Framer Motion for fluid UI animations. For intelligence, we integrated Gemini 2.0 Flash to deliver rapid route analysis while using OpenAI GPT-3.5 as a fallback layer to maintain reliability. ElevenLabs was used to generate natural voice guidance that enables hands-free navigation. Windborne Systems provided high-resolution atmospheric data, while Mapbox enabled custom campus geospatial overlays. Auth0 was implemented to handle secure authentication for campus users.

### Challenges we ran into

One of our biggest technical challenges was synchronizing real-time wind data with complex campus path geometries. Translating environmental conditions into accurate effort and comfort scores required building a custom scoring model. We also encountered deployment challenges related to managing authentication callbacks across dynamic Vercel environments. Additionally, coordinating real-time AI voice feedback required building logic to prevent overlapping audio during rapid navigation updates.

### Accomplishments we're proud of

We are especially proud of the UI and experience quality achieved across devices. Creating a layout that feels polished on desktop while remaining intuitive on mobile was a major design milestone. From a technical perspective, successfully chaining Gemini’s reasoning output with ElevenLabs voice synthesis enabled a fully autonomous voice-guided navigation experience, which felt like a step toward next-generation spatial computing.

### What we learned

SlugRoute became a masterclass in system orchestration. We learned that impactful AI systems are built through thoughtful integration of sensors, real-time data pipelines, cloud infrastructure, and user interfaces. The project strengthened our understanding of production deployment workflows, environment variable management, authentication configuration, and AI-to-voice pipelines.

### What's next

Our roadmap includes expanding SlugRoute into augmented reality navigation by projecting wind-sheltered routes directly onto the user’s camera feed. We also plan to introduce predictive crowd modeling using historical campus traffic patterns to help students avoid peak congestion. Another major feature in development is group routing, which will calculate optimal shared meeting points and synchronize routes for multiple users simultaneously.

## 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: 19 recognized source files, 112 KB.
- CSS (language) — detected in the code
- Google Gemini (technology) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- Python (language) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (24 of 24)

```
.gitignore
app/api/auth0-test/route.ts
app/api/chat/route.ts
app/api/explain/route.ts
app/api/routes/route.ts
app/api/trails/route.ts
app/api/voice/route.ts
app/api/wind/route.ts
app/globals.css
app/layout.tsx
app/page.tsx
components/ChatWidget.tsx
components/LoginOverlay.tsx
components/Map.tsx
components/RouteCard.tsx
eslint.config.mjs
lib/auth0.ts
next.config.ts
package.json
postcss.config.mjs
proxy.ts
README.md
tsconfig.json
types/index.ts
```

### Dependencies

- package.json: @auth0/nextjs-auth0@^4.14.0, @google/generative-ai@^0.24.1, @tailwindcss/postcss@^4, @types/mapbox-gl@^3.4.1, @types/node@^20, @types/react@^19, @types/react-dom@^19, clsx@^2.1.1, eslint@^9, eslint-config-next@16.1.3, framer-motion@^12.26.2, lucide-react@^0.562.0, mapbox-gl@^3.18.0, next@16.1.3, openai@^6.16.0, react@19.2.3, react-dom@19.2.3, react-map-gl@^8.1.0, tailwind-merge@^3.4.0, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- Fix search bar: Implement filtering and expand campus locations
- Final cleanup: Remove unused imports and dead code
- Rename project to SlugRoute
- Fix audio overlap issue
- Fix Trail type definition for strict build
- Final cleanup: Fix lint errors and remove temp scripts
- first push
- Initial commit from Create Next App

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

### package.json

```
{
  "name": "slugroute",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@auth0/nextjs-auth0": "^4.14.0",
    "@google/generative-ai": "^0.24.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.26.2",
    "lucide-react": "^0.562.0",
    "mapbox-gl": "^3.18.0",
    "next": "16.1.3",
    "openai": "^6.16.0",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "react-map-gl": "^8.1.0",
    "tailwind-merge": "^3.4.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/mapbox-gl": "^3.4.1",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.1.3",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}
```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Auth0Provider } from '@auth0/nextjs-auth0';
import "./globals.css";

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

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

export const metadata: Metadata = {
  title: "SlugRoute | Campus Navigation",
  description: "Advanced campus navigation for UCSC",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
        suppressHydrationWarning
      >
        <Auth0Provider>
          {children}
        </Auth0Provider>
      </body>
    </html>
  );
}

```

### types/index.ts

```typescript
export interface Route {
    id: string;
    name: string;
    type: string;
    distance: number;
    time: number;
    effort: number;
    windExposure: string;
    scenic_score: number;
    color: string;
    path: number[][];
}

export interface Buddy {
    id: string;
    name: string;
    locationName: string;
    coords: [number, number];
    status: string;
}

export interface Activity {
    title: string;
    icon: string;
    coords: [number, number];
    desc: string;
}

export interface WindData {
    speed: number;
    direction: number;
    temp: number;
    condition: string;
    forecast?: ForecastDay[];
}

export interface ForecastDay {
    day: string;
    icon: string;
    condition: string;
    temp: number;
}

export interface Trail {
    id: string;
    latitude: number;
    longitude: number;
    name: string;
    difficulty?: string;
    stars?: number;
    summary?: string;
    // Add other trail properties as needed based on API response
    [key: string]: unknown;
}

```

### app/api/auth0-test/route.ts

```typescript

import { NextResponse } from 'next/server';
import { getManagementApiToken } from '@/lib/auth0';

export async function GET() {
    try {
        const token = await getManagementApiToken();

        // Return a masked version of the token for verification
        const maskedToken = `${token.substring(0, 10)}...${token.substring(token.length - 10)}`;

        return NextResponse.json({
            success: true,
            message: 'Successfully retrieved Auth0 Management API token',
            tokenPreview: maskedToken
        });
    } catch (error) {
        console.error('API Verification Error:', error);
        return NextResponse.json(
            { success: false, error: 'Failed to retrieve token' },
            { status: 500 }
        );
    }
}

```

### app/api/voice/route.ts

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

export async function POST(request: NextRequest) {
    try {
        const { text } = await request.json();

        if (!process.env.ELEVENLABS_API_KEY) {
            console.warn('Missing ELEVENLABS_API_KEY');
            return NextResponse.json({ audioUrl: '/demo_voice_alert.mp3', mock: true });
        }

        const voiceId = '21m00Tcm4TlvDq8ikWAM'; // Rachel
        const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
            method: 'POST',
            headers: {
                'xi-api-key': process.env.ELEVENLABS_API_KEY,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                text: text || "Wind warning on your route.",
                model_id: "eleven_turbo_v2",
                voice_settings: {
                    stability: 0.5,
                    similarity_boost: 0.5,
                }
            }),
        });

        if (!response.ok) {
            const errorText = await response.text();
            console.error('ElevenLabs API Error:', errorText);
            // Fallback to mock on error
            return NextResponse.json({ audioUrl: '/demo_voice_alert.mp3', mock: true });
        }

        const audioBuffer = await response.arrayBuffer();

        return new NextResponse(audioBuffer, {
            headers: {
                'Content-Type': 'audio/mpeg',
                'Content-Length': audioBuffer.byteLength.toString(),
            },
        });

    } catch (error) {
        console.error('Voice Generation Error:', error);
        return NextResponse.json({ audioUrl: '/demo_voice_alert.mp3', mock: true });
    }
}

```

### app/api/routes/route.ts

```typescript
import { NextResponse } from 'next/server';

// UCSC DEMO ROUTES (Science Hill -> Stevenson)
// Mocking the geometry (lat/lons) for the demo.
const ROUTES = [
    {
        id: 'fastest',
        name: 'Fastest (Steep)',
        type: 'fastest',
        distance: 0.8,
        time: 12,
        effort: 8.5, // High effort due to hills
        scenic_score: 5,
        windExposure: 'High', // Exposed on ridge
        color: '#3b82f6', // blue
        path: [ // Simplified generic path
            [36.9997, -122.0620], // Science Hill
            [36.9980, -122.0600],
            [36.9960, -122.0550], // Stevenson
        ]
    },
    {
        id: 'easiest',
        name: 'Easiest (Wind Sheltered)',
        type: 'easiest',
        distance: 1.1,
        time: 14, // Slightly longer
        effort: 4.0, // Much lower effort!
        scenic_score: 7,
        windExposure: 'Low', // Protected by trees
        color: '#22c55e', // green
        path: [
            [36.9997, -122.0620],
            [36.9990, -122.0640], // Detour through trees
            [36.9970, -122.0630],
            [36.9960, -122.0590],
            [36.9960, -122.0550],
        ]
    },
    {
        id: 'scenic',
        name: 'Scenic (Redwood Loop)',
        type: 'scenic',
        distance: 1.4,
        time: 18,
        effort: 5.5,
        scenic_score: 10,
        windExposure: 'Medium',
        color: '#a855f7', // purple
        path: [
            [36.9997, -122.0620],
            [37.0010, -122.0610], // Up into Pogonip
            [37.0000, -122.0580],
            [36.9980, -122.0550],
            [36.9960, -122.0550],
        ]
    }
];

export async function GET() {
    // In a real app, this would take start/end points and run A* with wind weights.
    // For the Hackathon Demo, we return these pre-calculated options.
    return NextResponse.json(ROUTES);
}

```

### app/api/explain/route.ts

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

export async function POST(request: NextRequest) {
    try {
        const { routeName, advantages, windSpeed } = await request.json();

        // MOCK RESPONSE for Hackathon speed/reliability
        // If no key is provided, return a canned response suited for the demo scenario.
        if (!process.env.OPENAI_API_KEY) {
            return NextResponse.json({
                explanation: `I recommend the **${routeName}** route. Although it is slightly longer, it shields you from the ${windSpeed}mph headwinds on the ridge, effectively reducing your cycling effort by 15%. It also passes the Redwood Grove, which is beautiful right now.`
            });
        }

        // OPENAI QUOTA EXCEEDED FALLBACK
        // Since the key has no credits, we simulate the AI insight with smart templates.

        let insight = "";
        const exposure = advantages || "Unknown";
        const speed = parseFloat(windSpeed || "0");

        if (speed > 5 && (exposure === 'High' || exposure === 'Medium')) {
            insight = `Caution: Winds are currently ${speed} m/s. The **${routeName}** route is exposed, so expect significant resistance on the ridge.`;
        } else if (speed > 5 && exposure === 'Low') {
            insight = `Good choice! With winds at ${speed} m/s, strictly following the **${routeName}** path will keep you sheltered by the tree line, saving ~15% energy.`;
        } else if (speed <= 5) {
            insight = `Conditions are mild (${speed} m/s). The **${routeName}** route offers the best detailed views of the bay today without wind chill concerns.`;
        } else {
            insight = `I recommend the **${routeName}** route. It optimizes for your energy by avoiding the crosswinds near Science Hill.`;
        }

        return NextResponse.json({
            explanation: insight
        });

    } catch (err) {
        console.error("Explanation Error:", err);
        return NextResponse.json({ explanation: "The route was chosen to minimize wind resistance based on current conditions." });
    }
}

```

### app/api/trails/route.ts

```typescript
import { NextResponse } from 'next/server';

export async function GET() {
    // Mock Data for Santa Cruz Trails
    const trails = [
        {
            id: '1',
            name: 'Pogonip Creek Nature Loop',
            summary: 'Beautiful shaded trail near UCSC.',
            difficulty: 'Easy',
            stars: 4.5,
            latitude: 37.0050,
            longitude: -122.0450,
            length: 3.5,
            url: 'https://www.hikingproject.com/trail/12345/pogonip',
            imgSqSmall: '',
        },
        {
            id: '2',
            name: 'U-Con Trail',
            summary: 'Popular connector for bikers and hikers.',
            difficulty: 'Intermediate',
            stars: 4.8,
            latitude: 37.0000,
            longitude: -122.0530,
            length: 2.1,
            url: '',
            imgSqSmall: '',
        },
        {
            id: '3',
            name: 'Old Growth Loop',
            summary: 'See the giant redwoods.',
            difficulty: 'Easy',
            stars: 5.0,
            latitude: 37.0120,
            longitude: -122.0550,
            length: 1.2,
            url: '',
            imgSqSmall: '',
        },
        {
            id: '4',
            name: 'Wilder Ranch Coastal Trail',
            summary: 'Stunning ocean views.',
            difficulty: 'Intermediate',
            stars: 4.9,
            latitude: 36.9600,
            longitude: -122.0830,
            length: 4.5,
            url: '',
            imgSqSmall: '',
        },
        {
            id: '5',
            name: 'DeLaveaga Park Loop',
            summary: 'Disc golf and nice woods.',
            difficulty: 'Easy',
            stars: 4.2,
            latitude: 37.0150,
            longitude: -122.0100,
            length: 2.5,
            url: '',
            imgSqSmall: '',
        },
        {
            id: '6',
            name: 'West Crack (MTB)',
            summary: 'Technical downhill track.',
            difficulty: 'Advanced',
            stars: 4.7,
            latitude: 37.0020,
            longitude: -122.0680,
            length: 1.0,
            url: '',
            imgSqSmall: '',
        },
        {
            id: '7',
            name: 'Garden of Eden',
            summary: 'Popular swimming hole trail.',
            difficulty: 'Moderate',
            stars: 4.6,
            latitude: 37.0300,
            longitude: -122.0580,
            length: 1.5,
            url: '',
            imgSqSmall: '',
        },
        {
            id: '8',
            name: 'Top of the World',
            summary: 'Best sunset spot.',
            difficulty: 'Intermediate',
            stars: 4.8,
            latitude: 37.0100,
            longitude: -122.0300,
            length: 0.8,
            url: '',
            imgSqSmall: '',
        },
    ];

    return NextResponse.json({ trails });
}

```

### app/api/wind/route.ts

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

export async function GET(request: NextRequest) {
    const searchParams = request.nextUrl.searchParams;
    const lat = searchParams.get('lat') || '36.9914'; // Default to UCSC Science Hill
    const lon = searchParams.get('lon') || '-122.0609';

    const clientId = process.env.WINDBORNE_CLIENT_ID;
    const apiKey = process.env.WINDBORNE_API_KEY;

    // MOCK DATA for UCSC Demo
    // Simulates a strong NW wind (common in Santa Cruz)
    const mockWindData = {
        speed_ms: 8.5, // ~19mph, breezy!
        direction_deg: 315, // NW
        gust_ms: 12.0,
        source: 'MOCK_UCSC_DATA'
    };

    if (!clientId || !apiKey) {
        console.warn('WindBorne keys missing, using mock UCSC wind data.');
        return NextResponse.json(mockWindData);
    }

    try {
        // Attempt real API call using Basic Auth
        // User reports WindBorne uses Basic Auth (User:Client ID, Pass:API Key)
        const authString = Buffer.from(`${clientId}:${apiKey}`).toString('base64');

        const response = await fetch(
            `https://api.windbornesystems.com/forecasts/v1/wm-4/point_forecast.json?coordinates=${lat},${lon}`,
            {
                method: 'GET',
                headers: {
                    'Authorization': `Basic ${authString}`,
                },
            }
        );

        if (!response.ok) {
            console.warn(`WindBorne API error (${response.status}). Using mock data.`);
            return NextResponse.json(mockWindData);
        }

        const data = await response.json();

        // Parse Forecast (Next 5 Days / 5 Points)
        // The API returns time steps. We'll take every 6th point (assuming ~hourly or 3-hourly) to simulate daily
        // or just take the first 5 available points for the "Forecast"
        const rawPoints = data.forecasts?.[0] || [];

        // Helper to process a point
        const processPoint = (p: any, i: number) => { // eslint-disable-line @typescript-eslint/no-explicit-any
            const u = p.wind_u_10m ?? 0;
            const v = p.wind_v_10m ?? 0;
            const temp = p.temperature_2m ?? 0;
            const precip = p.precipitation ?? 0;
            const speed = Math.sqrt(u * u + v * v);

            let condition = 'Cloudy';
            if (precip > 0.1) condition = 'Rainy';
            else if (speed > 8) condition = 'Windy';
            else if (temp > 20) condition = 'Sunny';
            else if (temp < 12) condition = 'Foggy';
            else if (temp > 15) condition = 'Partly Cloudy';

            return {
                day: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][(new Date().getDay() + i) % 7],
                temp: temp,
                condition: condition,
                icon: condition === 'Sunny' ? '☀️' : condition === 'Rainy' ? '🌧️' : '☁️'
            };
        };

        const forecast = rawPoints.slice(0, 5).map((p: any, i: number) => processPoint(p, i)); // eslint-disable-line @typescript-eslint/no-explicit-any

        // Current (first point)
        const current = rawPoints[0] || {};
        const u = current.wind_u_10m ?? 0;
        const v = current.wind_v_10m ?? 0;
        const dir = (Math.atan2(v, u) * 180) / Math.PI;
        const metDir = ((dir + 360) % 360 + 180) % 360;

        return NextResponse.json({
            speed: parseFloat(Math.sqrt(u * u + v * v).toFixed(1)),
            direction: Math.round(metDir),
            temp: current.temperature_2m ?? 15,
            condition: processPoint(current, 0).condition,
            forecast: forecast.length > 0 ? forecast : [
                { day: 'Mon', temp: 15, condition: 'Sunny', icon: '☀️' },
                { day: 'Tue', temp: 16, condition: 'Cloudy', icon: '☁️' },
                { day: 'Wed', temp: 14, condition: 'Rainy', icon: '🌧️' },
                { day: 'Thu', temp: 18, condition: 'Sunny', icon: '☀️' },
                { day: 'Fri', temp: 17, condition: 'Partly Cloudy', icon: '⛅' }
            ]
        });

    } catch (error) {
        console.error('Wind API Error', error);
        // Fallback Mock with Forecast
        return NextResponse.json({
            speed: 8.5,
            direction: 315,
            temp: 15,
            condition: 'Cloudy',
            forecast: [
                { day: 'Mon', temp: 15, condition: 'Sunny', icon: '☀️' },
                { day: 'Tue', temp: 16, condition: 'Cloudy', icon: '☁️' },
                { day: 'Wed', temp: 14, condition: 'Rainy', icon: '🌧️' },
                { day: 'Thu', temp: 18, condition: 'Sunny', icon: '☀️' },
                { day: 'Fri', temp: 17, condition: 'Partly Cloudy', icon: '⛅' }
            ]
        });
    }
}

```

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

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { GoogleGenerativeAI } from '@google/generative-ai';

export async function POST(request: NextRequest) {
    let message = "", context: Record<string, unknown> = {};

    try {
        const body = await request.json();
        message = body.message;
        context = body.context;

        if (!process.env.GEMINI_API_KEY) {
            return NextResponse.json(
                { text: "System Error: Gemini API Key is missing. Please check .env.local." },
                { status: 500 }
            );
        }

        const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
        const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });

        // Construct System Prompt with Context
        const systemPrompt = `
You are 'Gemini Guide', an intelligent AI assistant for the 'SlugRoute' navigation app at UC Santa Cruz.
Your goal is to help users navigate, find friends, and check weather conditions.

CURRENT APP CONTEXT:
- Weather: ${context.temp}°F, ${context.condition}
- Active Tab: ${context.activeTab}
- User Location: Campus (implied)

CAPABILITIES:
You can control the app by including specific "Action Tags" at the end of your response.
- If the user asks to see the weather: reply helpfully, then end with [ACTION: NAVIGATE_WEATHER]
- If the user asks to see friends/buddies: reply helpfully, then end with [ACTION: NAVIGATE_FRIENDS]
- If the user asks about activities or community: reply helpfully, then end with [ACTION: NAVIGATE_COMMUNITY]
- If the user asks to plan a route or go to map: reply helpfully, then end with [ACTION: NAVIGATE_MAP]

RULES:
- Keep answers concise (under 2 sentences) as the user is likely mobile.
- Be friendly, helpful, and "Slug-themed" (energetic, nature-loving).
- Do NOT hallucinate features not in the app.
        `.trim();

        const chat = model.startChat({
            history: [
                {
                    role: "user",
                    parts: [{ text: systemPrompt }],
                },
                {
                    role: "model",
                    parts: [{ text: "Understood. I am Gemini Guide, ready to assist on SlugRoute." }],
                },
            ],
        });

        const result = await chat.sendMessage(message);
        const response = await result.response;
        const text = response.text();

        return NextResponse.json({ text });

    } catch (error: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
        console.error("Gemini Chat Error:", error);

        // FALLBACK: Try OpenAI if Gemini fails (Quota or specific error)
        if (process.env.OPENAI_API_KEY) {
            try {
                console.log("Attempting OpenAI Fallback...");
                const { OpenAI } = await import("openai");
                const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

                const completion = await openai.chat.completions.create({
                    messages: [
                        {
                            role: "system", content: `
You are 'Gemini Guide', an intelligent AI assistant for the 'SlugRoute' navigation app at UC Santa Cruz.
Your goal is to help users navigate, find friends, and check weather conditions.

CURRENT APP CONTEXT:
- Weather: ${context.temp}°F, ${context.condition}
- Active Tab: ${context.activeTab}
- User Location: Campus (implied)

CAPABILITIES:
You can control the app by including specific "Action Tags" at the end of your response.
- If the user asks to see the weather: reply helpfully, then end with [ACTION: NAVIGATE_WEATHER]
- If the user asks to see friends/buddies: reply helpfully, then end with [ACTION: NAVIGATE_FRIENDS]
- If the user asks about activities or community: reply helpfully, then end with [ACTION: NAVIGATE_COMMUNITY]
- If the user asks to plan a route or go to map: reply helpfully, then end with [ACTION: NAVIGATE_MAP]

RULES:
- Keep answers concise (under 2 sentences) as the user is likely mobile.
- Be friendly, helpful, and "Slug-themed" (energetic, nature-loving).
- Do NOT hallucinate features not in the app.
- Even though you are powered by OpenAI right now, you MUST identifying yourself as 'Gemini Guide'.
                        `.trim()
                        },
                        { role: "user", content: message }
                    ],
                    model: "gpt-3.5-turbo",
                });

                const text = completion.choices[0].message.content || "I'm lost in the fog.";
                return NextResponse.json({ text });

            } catch (openaiError: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
                console.error("OpenAI Fallback Error:", openaiError);
                if (openaiError?.status === 429) {
                    return NextResponse.json(
                        { text: "My brain is double-tired! Both Gemini and OpenAI quotas are exhausted. Please try again later." },
                        { status: 429 }
                    );
                }
            }
        }

        // Handle Quota Exceeded
        if (error.status === 429 || error.message?.includes("429") || error.message?.includes("Quota exceeded")) {
            return NextResponse.json(
                { text: "My brain is a bit tired (Quota Exceeded). Please wait a minute and try again!" },
                { status: 429 }
            );
        }

        return NextResponse.json(
            { text: "Sorry, I'm having trouble connecting to the stars right now. Please try again." },
            { status: 500 }
        );
    }
}

```

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