# Project export: StepOne

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: Empowering founders with smarter customer discovery, actionable insights, and simulate interviews.
- Devpost: https://devpost.com/software/project-vzdki4rnjqu0
- GitHub: https://github.com/poudelef/Step1
- Video: https://player.vimeo.com/video/1095430328?byline=0&portrait=0&title=0#t=
- Team: 2 GitHub contributor(s) — Sambhav (13 commits), varungolusupudi (6 commits)

## Devpost submission (written by the team)

### Inspiration

Over 90% of startups fail, with the lack of market need cited as the #1 reason for failure (CB Insights, 2018). At the heart of this issue is a broken or non-existent customer discovery process. Despite the popularity of frameworks like Lean Startup, most founders still struggle with the how of effective customer interviews. Today, with AI the technical barriers to developing applications and integrating systems have been greatly eliminated. However, we believe the greater challenge now lies in understanding market demands and aligning with what customers truly want. We recognized that founders often enter interviews with biases, ask leading questions, and hear only what they want to hear. As a result, they build products based on assumptions—not reality. According to Batova et al. (2016), this overconfidence and lack of qualitative rigor in lean startup customer discovery creates a "validation void" that can doom ventures early. So we decided to build something to aid customer discovery for the next generation of builders.

### What it does

Our solution is an AI-powered Customer Discovery Agent. We offer a smart, interactive platform that simulates real customer conversations. Founders can pitch their ideas to diverse AI personas (like Sarah, a single mom), engage in guided interviews, and receive real-time analysis of pain points, competitive threats, and unmet needs. Our Three-Step Approach Pitch Your Idea: Founders describe their startup idea and select relevant personas. Practice Interviews: Natural language interviews help founders learn to ask open-ended, unbiased questions. Insights & Feedback: A reasoning agent provides an instant summary of key insights, highlights areas of concern, and offers suggestions for better questioning. This is more than just practice - it's a training ground that replicates real discovery conditions, while helping founders break out of their confirmation bias loops.

### How we built it

Our project architecture includes the following key agents: Interview Agent: Realistic AI personas powered by language models. Coach Agent: Offers tips, critiques, and suggests follow-up questions. Market Research Agent: Pulls contextual data (news, product recalls, sentiment analysis). Orchestrator: Coordinates all agents and manages the feedback loop. The platform leverages LLM APIs for persona dialogue, custom prompt engineering for interview realism, and backend data pipelines to extract and synthesize key insights from each conversation.

### Challenges we ran into

Avoiding Leading Questions: Just as real founders struggle to avoid leading the interview, we had to train our Coach Agent to detect and flag such behavior. This required curating hundreds of bad vs. good question examples.

### Accomplishments we're proud of

We're proud of is how our system empowers founders of all backgrounds to conduct customer discovery with confidence. Integrating these different agentic systems, and ensuring they behave as reliably as possible was a challenge....

### What we learned

Customer discovery is a skill, and like any skill it can be taught and practiced. Lean Startup methods often lack practical guidance on how to conduct interviews (Batova et al., 2016). Many startups focus too much on building and not enough on validating. By integrating learning loops into discovery practice, we can help founders build what people actually want. This experience also reinforced our belief in the potential of AI not just to automate tasks, but to coach and educate entrepreneurs in ways that scale. Our Vision -

### What's next

in the pipeline :) Real-time suggestions for what questions to ask next (leveraging feedback agents that monitor conversations and provide timely prompts). Expanded persona library featuring a wider diversity of demographics, accents, and conversational styles to ensure more inclusive and relatable interactions. We wish to add support for video interviews, allowing users to practice in a face-to-face setting. References Customer Discovery Basics and Best Practices Darren Cooke: https://www.youtube.com/watch?v=sW61nRfNjgg Batova, T., Clark, D., & Card, D. (2016). Challenges of lean customer discovery as invention. 2016 IEEE International Professional Communication Conference (IPCC), 1-8. York, J. M. (2020). What is lean startup: A solution to startup failures or not? International Journal of Tourism & Hotel Business Management, 2(2), 250-264.

## README (from the GitHub repository)

# StepOne

## Introduction

Over 90% of startups fail, with the lack of market need cited as the #1 reason for failure (CB Insights, 2018). At the heart of this issue is a broken or non-existent customer discovery process. Despite the popularity of frameworks like Lean Startup, most founders still struggle with the how of effective customer interviews.


We recognized that founders often enter interviews with biases, ask leading questions, and hear only what they want to hear. As a result, they build products based on assumptions—not reality. According to Batova et al. (2016), this overconfidence and lack of qualitative rigor in lean startup customer discovery creates a "validation void" that can doom ventures early.

So, we set out to build something that could help.

## What it does

Our solution is an AI-powered Customer Discovery Agent—a smart, interactive platform that simulates real customer conversations. Founders can pitch their ideas to diverse AI personas (like Sarah, a single mom), engage in guided interviews, and receive real-time analysis of pain points, competitive threats, and unmet needs.

**Our Three-Step Approach**

1. Pitch Your Idea: Founders describe their startup idea and select relevant personas.

2. Practice Interviews: Natural language interviews help founders learn to ask open-ended, unbiased questions.

3. Insights & Feedback: A reasoning agent provides an instant summary of key insights, highlights areas of concern, and offers suggestions for better questioning.

This is more than just practice—it's a training ground that replicates real discovery conditions, while helping founders break out of their confirmation bias loops.

## How to run the code

**Run frontend**

    npm install 
    npm run dev

**Run backend**

Make sure you have python and pip installed
  
      cd backend
  
Install required libraries:
  
    fastapi==0.109.2
    httpx==0.26.0
    uvicorn==0.27.1
    python-dotenv==1.0.1
    pydantic==2.5.3
    fastapi[standard]
    protobuf<3.20,>=3.9.2
    openai-whisper==20231117
    torch==2.2.0
    torchaudio==2.2.0
    python-multipart==0.0.9  # For handling file uploads
    numpy<1.26.0,>=1.18.5
    google-auth-oauthlib<0.5,>=0.4.1
    
OR

    pip install -r requirements.txt

To run server, use command

    fastapi run main.py


## Detected evidence (automated analysis)

Indexed codebase: 29 recognized source files, 298 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Anthropic (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (36 of 36)

```
.gitignore
backend/.gitkeep
backend/demo_voice.py
backend/main.py
backend/README.md
backend/requirements.txt
backend/setup.py
backend/start_voice_demo.sh
backend/test_api.py
database-schema.sql
frontend/eslint.config.mjs
frontend/jsconfig.json
frontend/next.config.mjs
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/src/app/auth/callback/route.js
frontend/src/app/dashboard/page.js
frontend/src/app/globals.css
frontend/src/app/layout.js
frontend/src/app/page.js
frontend/src/app/page.module.css
frontend/src/app/validate/page.js
frontend/src/components/AnimatedCounter.js
frontend/src/components/AuthModal.js
frontend/src/components/ThemeToggle.js
frontend/src/components/VapiWidget.js
frontend/src/components/VoiceOnlyInterview.js
frontend/src/contexts/AuthContext.js
frontend/src/lib/api.js
frontend/src/lib/coaching.js
frontend/src/lib/database.js
frontend/src/lib/supabase-server.js
frontend/src/lib/supabase.js
frontend/tailwind.config.js
README.md
```

### Dependencies

- backend/requirements.txt: fastapi@==0.104.1, httpx@==0.25.0, pdfkit@==1.0.0, pydantic@==2.4.2, python-dotenv@==1.0.0, python-multipart@==0.0.6, uvicorn[standard]@==0.24.0, websockets@==12.0
- frontend/package.json: @heroicons/react@^2.1.1, @supabase/auth-ui-react@^0.4.7, @supabase/auth-ui-shared@^0.1.8, @supabase/ssr@^0.6.1, @supabase/supabase-js@^2.50.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, autoprefixer@^10.0.1, eslint@^8, eslint-config-next@14.1.0, framer-motion@^11.0.3, next@14.1.0, postcss@^8, react@^18, react-dom@^18, tailwindcss@^3.4.1, typescript@^5

### Recent commits (newest first)

- Changed title
- Removed image
- Final Commit
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Merge pull request #1 from poudelef/personas
- Working prototype
- Working Voice
- Working Validation and AI personas

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

### backend/requirements.txt

```
fastapi==0.104.1
uvicorn[standard]==0.24.0
httpx==0.25.0
python-dotenv==1.0.0
pydantic==2.4.2
python-multipart==0.0.6
pdfkit==1.0.0
websockets==12.0 
```

### frontend/package.json

```
{
  "name": "validate-ai-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@heroicons/react": "^2.1.1",
    "@supabase/auth-ui-react": "^0.4.7",
    "@supabase/auth-ui-shared": "^0.1.8",
    "@supabase/ssr": "^0.6.1",
    "@supabase/supabase-js": "^2.50.0",
    "framer-motion": "^11.0.3",
    "next": "14.1.0",
    "react": "^18",
    "react-dom": "^18",
    "tailwindcss": "^3.4.1"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.1.0",
    "postcss": "^8",
    "typescript": "^5"
  }
}

```

### frontend/src/app/layout.js

```javascript
import "./globals.css";
import { AuthProvider } from '../contexts/AuthContext';

export const metadata = {
  title: "StepOne - Stop Building Blind",
  description: "Validate any startup idea in 5 minutes. Get real-sounding customer feedback, question coaching, and deck-ready insights—all powered by AI.",
  keywords: "startup validation, customer discovery, AI personas, mock interviews, startup ideas",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="antialiased" suppressHydrationWarning={true}>
        <AuthProvider>
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}

```

### frontend/src/app/auth/callback/route.js

```javascript
import { createClient } from '../../../lib/supabase-server'
import { NextResponse } from 'next/server'

export async function GET(request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')
  const next = searchParams.get('next') ?? '/'

  if (code) {
    const supabase = createClient()
    const { error } = await supabase.auth.exchangeCodeForSession(code)
    if (!error) {
      return NextResponse.redirect(`${origin}${next}`)
    }
  }

  // return the user to an error page with instructions
  return NextResponse.redirect(`${origin}/auth/auth-code-error`)
} 
```

### frontend/src/app/page.js

```javascript
'use client';

import { useState, useEffect } from 'react';
import { motion, useScroll, useTransform, useInView } from 'framer-motion';
import AnimatedCounter from '../components/AnimatedCounter';
import ThemeToggle from '../components/ThemeToggle';
import AuthModal from '../components/AuthModal';
import { useAuth } from '../contexts/AuthContext';
import { useRouter } from 'next/navigation';

export default function Home() {
  const [isVisible, setIsVisible] = useState(false);
  const [showAuthModal, setShowAuthModal] = useState(false);
  const [authMode, setAuthMode] = useState('signin');
  const { scrollY } = useScroll();
  const y1 = useTransform(scrollY, [0, 300], [0, 100]);
  const y2 = useTransform(scrollY, [0, 300], [0, -100]);
  
  const { user, loading, signOut } = useAuth();
  const router = useRouter();

  useEffect(() => {
    setIsVisible(true);
    
    // Redirect authenticated users to dashboard
    if (!loading && user) {
      router.push('/dashboard');
    }
  }, [user, loading, router]);

  const handleSignIn = () => {
    setAuthMode('signin');
    setShowAuthModal(true);
  };

  const handleSignUp = () => {
    setAuthMode('signup');
    setShowAuthModal(true);
  };

  const handleSignOut = async () => {
    await signOut();
  };

  if (loading) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-white via-blue-50 to-purple-50 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 flex items-center justify-center">
        <div className="text-center">
          <div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-500 dark:from-blue-500 dark:to-cyan-400 rounded-xl flex items-center justify-center mx-auto mb-4 shadow-lg animate-pulse">
            <span className="text-white font-bold text-2xl">V</span>
          </div>
          <p className="text-gray-600 dark:text-slate-300">Loading...</p>
        </div>
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gradient-to-br from-white via-blue-50 to-purple-50 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 relative overflow-hidden transition-colors duration-500">
      {/* Animated Background Elements */}
      <div className="fixed inset-0 overflow-hidden pointer-events-none">
        <motion.div
          className="absolute -top-40 -right-40 w-80 h-80 bg-gradient-to-r from-pink-400/20 to-purple-400/20 dark:from-blue-500/10 dark:to-purple-500/10 rounded-full blur-3xl"
          animate={{
            scale: [1, 1.2, 1],
            opacity: [0.3, 0.6, 0.3],
          }}
          transition={{
            duration: 8,
            repeat: Infinity,
            ease: "easeInOut"
          }}
        />
        <motion.div
          className="absolute -bottom-40 -left-40 w-96 h-96 bg-gradient-to-r from-blue-400/20 to-cyan-400/20 dark:from-cyan-500/10 dark:to-teal-500/10 rounded-full blur-3xl"
          animate={{
            scale: [1.2, 1, 1.2],
            opacity: [0.4, 0.2, 0.4],
          }}
          transition={{
            duration: 10,
            repeat: Infinity,
            ease: "easeInOut"
          }}
        />
      </div>

      {/* Navigation */}
      <motion.nav 
        className="fixed top-0 w-full bg-white/80 dark:bg-slate-950/90 backdrop-blur-xl border-b border-gray-200/50 dark:border-slate-700/50 z-50 transition-colors duration-500"
        initial={{ y: -100 }}
        animate={{ y: 0 }}
        transition={{ duration: 0.8, ease: "easeOut" }}
      >
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div className="flex justify-between items-center py-4">
            <motion.div 
              className="flex items-center space-x-3"
              whileHover={{ scale: 1.05 }}
              transition={{ type: "spring", stiffness: 400 }}
            >
              <div className="relative">
                <div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-pink-500 dark:from-blue-500 dark:to-cyan-400 rounded-xl flex items-center justify-center shadow-lg shadow-purple-500/25 dark:shadow-blue-500/25">
                  <span className="text-white font-bold text-lg">V</span>
                </div>
                <motion.div
                  className="absolute inset-0 bg-gradient-to-br from-purple-500 to-pink-500 dark:from-blue-500 dark:to-cyan-400 rounded-xl opacity-75"
                  animate={{ scale: [1, 1.1, 1] }}
                  transition={{ duration: 2, repeat: Infinity }}
                />
              </div>
              <span className="text-gray-900 dark:text-white font-bold text-2xl tracking-tight transition-colors duration-300">StepOne</span>
            </motion.div>
            
            <div className="hidden md:flex space-x-8 items-center">
              {['Features', 'Demo', 'Pricing'].map((item, index) => (
                <motion.a
                  key={item}
                  href={`#${item.toLowerCase()}`}
                  className="text-gray-700 dark:text-slate-300 hover:text-purple-600 dark:hover:text-cyan-400 transition-colors font-medium"
                  initial={{ opacity: 0, y: -20 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ delay: index * 0.1 + 0.5 }}
                  whileHover={{ y: -2 }}
                >
                  {item}
                </motion.a>
              ))}
              <ThemeToggle />
            </div>
            
            <div className="flex items-center space-x-4">
              {user ? (
                <div className="flex items-center space-x-4">
                  <span className="text-gray-700 dark:text-slate-300 font-medium">
                    {user.email}
                  </span>
                  <motion.button
                    onClick={handleSignOut}
                    className="text-gray-700 dark:text-slate-300 hover:text-purple-600 dark:hover:text-cyan-400 px-4 py-2 rounded-lg font-medium transition-colors duration-300"
                    whileHover={{ scal
[truncated — 40193 more characters]
```

### frontend/src/app/validate/page.js

```javascript
'use client';

import { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useAuth } from '../../contexts/AuthContext';
import { useRouter } from 'next/navigation';
import VoiceOnlyInterview from '../../components/VoiceOnlyInterview';
import { ValidationDatabase } from '../../lib/database';

export default function ValidatePage() {
  const { user, loading: authLoading } = useAuth();
  const router = useRouter();
  
  // Main state
  const [currentStep, setCurrentStep] = useState('input'); // input, personas, interview, analysis, market, results
  const [idea, setIdea] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  
  // Step-specific state
  const [personas, setPersonas] = useState([]);
  const [selectedPersona, setSelectedPersona] = useState(null);
  const [voiceConversationHistory, setVoiceConversationHistory] = useState([]);
  const [insights, setInsights] = useState(null);
  const [marketAnalysis, setMarketAnalysis] = useState(null);
  
  // Progress tracking
  const [progress, setProgress] = useState(0);

  useEffect(() => {
    if (!authLoading && !user) {
      router.push('/');
    }
  }, [user, authLoading, router]);

  const handleStartValidation = async () => {
    if (!idea.trim()) {
      setError('Please enter your startup idea');
      return;
    }

    setLoading(true);
    setError('');

    try {
      // Call personas endpoint directly
      const response = await fetch('http://localhost:8000/personas', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          idea: idea,
          target_segment: ''
        })
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      setPersonas(data.personas);
      setProgress(0.25);
      setCurrentStep('personas');
    } catch (err) {
      setError('Failed to generate personas: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  const handleSelectPersona = async (personaIndex) => {
    setSelectedPersona(personas[personaIndex]);
    setVoiceConversationHistory([]);
    setCurrentStep('interview');
  };

  const handleVoiceInterviewComplete = async (conversationHistory) => {
    setLoading(true);
    setError('');

    try {
      // Analyze the voice conversation
      await handleAnalyzeVoiceInterview(conversationHistory);
      
    } catch (err) {
      setError('Failed to complete validation: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  const handleAnalyzeVoiceInterview = async (conversationHistory) => {
    setLoading(true);
    setError('');

    try {
      // Convert voice conversation history to the format expected by coach-ai
      const formattedConversation = conversationHistory.map(item => 
        `${item.role === 'user' ? 'Founder' : selectedPersona.name}: ${item.message}`
      );

      // Call coach-ai endpoint
      const response = await fetch('http://localhost:8000/coach-ai', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          idea: idea,
          conversation: formattedConversation
        })
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      setInsights(data);
      setProgress(0.75);
      setCurrentStep('analysis');
      
      // Save validation after analysis (in case user doesn't run market analysis)
      await saveValidationToDatabase(conversationHistory, data, null);
      
    } catch (err) {
      setError('Failed to analyze interview: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  const handleMarketAnalysis = async () => {
    setLoading(true);
    setError('');

    try {
      // Call market-ai endpoint
      const response = await fetch('http://localhost:8000/market-ai', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          idea: idea
        })
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      setMarketAnalysis(data);
      setProgress(1.0);
      setCurrentStep('market');
      
      // Save complete validation with market analysis
      await saveValidationToDatabase(voiceConversationHistory, insights, data);
      
    } catch (err) {
      setError('Failed to run market analysis: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  // Helper function to save validation data
  const saveValidationToDatabase = async (conversationHistory, insightsData, marketData) => {
    if (!user) {
      console.error('No user found - cannot save validation');
      return;
    }

    try {
      console.log('🔄 Attempting to save validation to database...');
      console.log('User ID:', user.id);
      console.log('Validation data:', {
        idea: idea,
        personas: personas?.length || 0,
        selectedPersona: selectedPersona?.name || 'None',
        conversationHistory: conversationHistory?.length || 0,
        insights: insightsData ? 'Present' : 'Missing',
        marketAnalysis: marketData ? 'Present' : 'Missing'
      });

      const validationData = {
        idea: idea,
        personas: personas,
        selectedPersona: selectedPersona,
        conversationHistory: conversationHistory,
        insights: insightsData,
        marketAnalysis: marketData,
        status: 'completed'
      };

      const saveResult = await ValidationDatabase.saveValidation(user.id, validationData);
      
      if (!saveResult.success) {
        console.error('❌ Failed to save validation:', saveResult.error);
[truncated — 20916 more characters]
```

### frontend/src/app/dashboard/page.js

```javascript
'use client';

import { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useAuth } from '../../contexts/AuthContext';
import { useRouter } from 'next/navigation';
import { ValidationDatabase } from '../../lib/database';
import { AICoachingService } from '../../lib/coaching';

export default function Dashboard() {
  const { user, loading, signOut } = useAuth();
  const router = useRouter();
  const [greeting, setGreeting] = useState('');
  const [dashboardData, setDashboardData] = useState({
    stats: null,
    validations: [],
    coachingDashboard: null,
    loading: true
  });
  const [activeTab, setActiveTab] = useState('overview');

  useEffect(() => {
    if (!loading && !user) {
      router.push('/');
    }

    // Set greeting based on time
    const hour = new Date().getHours();
    if (hour < 12) setGreeting('Good morning');
    else if (hour < 18) setGreeting('Good afternoon');
    else setGreeting('Good evening');

    // Load dashboard data
    if (user) {
      loadDashboardData();
    }
  }, [user, loading, router]);

  const loadDashboardData = async () => {
    try {
      const [statsResult, validationsResult, coachingResult] = await Promise.all([
        ValidationDatabase.getUserStats(user.id),
        ValidationDatabase.getUserValidations(user.id, 10),
        AICoachingService.getCoachingDashboard(user.id)
      ]);

      setDashboardData({
        stats: statsResult.success ? statsResult.stats : null,
        validations: validationsResult.success ? validationsResult.validations : [],
        coachingDashboard: coachingResult.success ? coachingResult.dashboard : null,
        loading: false
      });
    } catch (error) {
      console.error('Error loading dashboard data:', error);
      setDashboardData(prev => ({ ...prev, loading: false }));
    }
  };

  const handleSignOut = async () => {
    await signOut();
    router.push('/');
  };

  const startCoachingSession = async (sessionType) => {
    try {
      const result = await AICoachingService.createCoachingSession(user.id, sessionType);
      if (result.success) {
        router.push(`/coaching/${result.session.id}`);
      }
    } catch (error) {
      console.error('Error starting coaching session:', error);
    }
  };

  if (loading || dashboardData.loading) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-white via-blue-50 to-purple-50 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 flex items-center justify-center">
        <div className="text-center">
          <div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-500 dark:from-blue-500 dark:to-cyan-400 rounded-xl flex items-center justify-center mx-auto mb-4 shadow-lg animate-pulse">
            <span className="text-white font-bold text-2xl">V</span>
          </div>
          <p className="text-gray-600 dark:text-slate-300">Loading dashboard...</p>
        </div>
      </div>
    );
  }

  if (!user) return null;

  const { stats, validations, coachingDashboard } = dashboardData;

  return (
    <div className="min-h-screen bg-gradient-to-br from-white via-blue-50 to-purple-50 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 transition-colors duration-500">
      {/* Navigation */}
      <motion.nav 
        className="bg-white/80 dark:bg-slate-950/90 backdrop-blur-xl border-b border-gray-200/50 dark:border-slate-700/50"
        initial={{ y: -100 }}
        animate={{ y: 0 }}
        transition={{ duration: 0.8 }}
      >
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div className="flex justify-between items-center py-4">
            <motion.div 
              className="flex items-center space-x-3"
              whileHover={{ scale: 1.05 }}
            >
              <div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-pink-500 dark:from-blue-500 dark:to-cyan-400 rounded-xl flex items-center justify-center shadow-lg">
                <span className="text-white font-bold text-lg">V</span>
              </div>
              <span className="text-gray-900 dark:text-white font-bold text-2xl tracking-tight">StepOne</span>
            </motion.div>
            
            <div className="flex items-center space-x-4">
              <span className="text-gray-700 dark:text-slate-300 font-medium">
                {user.email}
              </span>
              <motion.button
                onClick={handleSignOut}
                className="text-gray-700 dark:text-slate-300 hover:text-purple-600 dark:hover:text-cyan-400 px-4 py-2 rounded-lg font-medium transition-colors duration-300"
                whileHover={{ scale: 1.05 }}
                whileTap={{ scale: 0.95 }}
              >
                Sign Out
              </motion.button>
            </div>
          </div>
        </div>
      </motion.nav>

      {/* Main Content */}
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
        {/* Welcome Section */}
        <motion.div
          className="text-center mb-12"
          initial={{ opacity: 0, y: 30 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.8 }}
        >
          <motion.h1 
            className="text-5xl md:text-6xl font-black text-gray-900 dark:text-white mb-4 tracking-tight"
            initial={{ opacity: 0, y: 50 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ delay: 0.2, duration: 0.8 }}
          >
            {greeting}! 👋
          </motion.h1>
          <motion.p 
            className="text-xl text-gray-600 dark:text-slate-300 mb-8"
            initial={{ opacity: 0, y: 30 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ delay: 0.4, duration: 0.8 }}
          >
            {stats?.totalValidations > 0 
              ? `You've completed ${stats.totalValidations} validation${stats.totalValidations > 1 ? 's' : ''}!`
              : 'Ready to validate your next big idea?'
   
[truncated — 21550 more characters]
```

### database-schema.sql

```sql
-- StepOne Database Schema
-- Run this in Supabase SQL Editor to set up validation tracking

-- Validations table
CREATE TABLE IF NOT EXISTS validations (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  idea TEXT NOT NULL,
  status VARCHAR(20) DEFAULT 'in_progress',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Personas table
CREATE TABLE IF NOT EXISTS personas (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  validation_id UUID REFERENCES validations(id) ON DELETE CASCADE,
  name VARCHAR(255) NOT NULL,
  role VARCHAR(255) NOT NULL,
  demographics TEXT,
  pain_points JSONB DEFAULT '[]',
  goals JSONB DEFAULT '[]',
  personality_traits JSONB DEFAULT '[]',
  communication_style TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Conversations table
CREATE TABLE IF NOT EXISTS conversations (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  validation_id UUID REFERENCES validations(id) ON DELETE CASCADE,
  persona_name VARCHAR(255),
  role VARCHAR(20) NOT NULL,
  message TEXT NOT NULL,
  sequence_number INTEGER,
  timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Insights table
CREATE TABLE IF NOT EXISTS insights (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  validation_id UUID REFERENCES validations(id) ON DELETE CASCADE,
  key_insights JSONB DEFAULT '[]',
  question_biases JSONB DEFAULT '[]',
  pain_points JSONB DEFAULT '[]',
  objections JSONB DEFAULT '[]',
  willingness_to_pay TEXT,
  feature_requests JSONB DEFAULT '[]',
  key_quotes JSONB DEFAULT '[]',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Market analysis table
CREATE TABLE IF NOT EXISTS market_analysis (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  validation_id UUID REFERENCES validations(id) ON DELETE CASCADE,
  competitor_heatmap JSONB DEFAULT '[]',
  trends JSONB DEFAULT '[]',
  value_propositions JSONB DEFAULT '[]',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Coaching sessions table
CREATE TABLE IF NOT EXISTS coaching_sessions (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  session_type VARCHAR(50) NOT NULL,
  topic VARCHAR(255),
  content JSONB,
  recommendations JSONB DEFAULT '[]',
  completed BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Enable Row Level Security
ALTER TABLE validations ENABLE ROW LEVEL SECURITY;
ALTER TABLE personas ENABLE ROW LEVEL SECURITY;
ALTER TABLE conversations ENABLE ROW LEVEL SECURITY;
ALTER TABLE insights ENABLE ROW LEVEL SECURITY;
ALTER TABLE market_analysis ENABLE ROW LEVEL SECURITY;
ALTER TABLE coaching_sessions ENABLE ROW LEVEL SECURITY;

-- Create policies for validations
CREATE POLICY "Users can view own validations" ON validations FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own validations" ON validations FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own validations" ON validations FOR UPDATE USING (auth.uid() = user_id);

-- Create policies for personas
CREATE POLICY "Users can view own personas" ON personas FOR SELECT USING (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);
CREATE POLICY "Users can insert own personas" ON personas FOR INSERT WITH CHECK (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);

-- Create policies for conversations
CREATE POLICY "Users can view own conversations" ON conversations FOR SELECT USING (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);
CREATE POLICY "Users can insert own conversations" ON conversations FOR INSERT WITH CHECK (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);

-- Create policies for insights
CREATE POLICY "Users can view own insights" ON insights FOR SELECT USING (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);
CREATE POLICY "Users can insert own insights" ON insights FOR INSERT WITH CHECK (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);

-- Create policies for market analysis
CREATE POLICY "Users can view own market analysis" ON market_analysis FOR SELECT USING (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);
CREATE POLICY "Users can insert own market analysis" ON market_analysis FOR INSERT WITH CHECK (
  auth.uid() = (SELECT user_id FROM validations WHERE id = validation_id)
);

-- Create policies for coaching sessions
CREATE POLICY "Users can view own coaching sessions" ON coaching_sessions FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own coaching sessions" ON coaching_sessions FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own coaching sessions" ON coaching_sessions FOR UPDATE USING (auth.uid() = user_id);

-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_validations_user_id ON validations(user_id);
CREATE INDEX IF NOT EXISTS idx_validations_created_at ON validations(created_at);
CREATE INDEX IF NOT EXISTS idx_personas_validation_id ON personas(validation_id);
CREATE INDEX IF NOT EXISTS idx_conversations_validation_id ON conversations(validation_id);
CREATE INDEX IF NOT EXISTS idx_insights_validation_id ON insights(validation_id);
CREATE INDEX IF NOT EXISTS idx_market_analysis_validation_id ON market_analysis(validation_id);
CREATE INDEX IF NOT EXISTS idx_coaching_sessions_user_id ON coaching_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_coaching_sessions_created_at ON coaching_sessions(created_at); 
```

### frontend/postcss.config.js

```javascript
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
} 
```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  darkMode: 'class',
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [],
} 
```

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