# Project export: Puzzled

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: TreeHacks 2026
- Tagline: Fixing what's broken, piece by piece.
- Devpost: https://devpost.com/software/puzzled-c1y9ub
- GitHub: https://github.com/Adas4044/puzzled
- Video: https://www.youtube.com/embed/gQ9hj49KBvs?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — yoonseoc126 (24 commits), Adam (23 commits), Leyna Huynh (15 commits)

## Devpost submission (written by the team)

### Inspiration

Between 40%-70% of medical devices and equipment in low and middle-income countries are broken, unused, or unfit for purpose. The problem is compounded by severe workforce shortages: many low-income countries have fewer than one biomedical engineer per 100,000 people. Thus 10–30% of donated equipment is ever put into operation. This means 70–90% ends up being unused or broken, not because it's useless, but because no one has the training, spare parts knowledge, or repair guidance to fix it. In many hospitals, technicians lack: Step-by-step repair documentation in their language Access to spare parts guidance Real-time troubleshooting support We built Puzzled to close this gap. Instead of donating more devices, what if we helped hospitals to fix the ones they already have?

### What it does

Puzzled is an computer vision powered visual repair assistant that turns any phone camera into a step-by-step repair guide. Users can: Select their familiar language Follow guided repair steps Capture a frame to verify each step Escalate to live expert help if needed Automatically sends a support request email with a Zoom meeting link to an expert. Automatically sends a support request email with a Zoom meeting link to an expert. Instead of leaving people puzzled in front of broken medical devices, lab equipment, household appliances, or even LEGO builds, we can help them find the missing piece 🧩— step by step.

### How we built it

Our app begins with a puzzle. When the final piece (our logo, featuring the adorable Treehacks llamas 🦙!) clicks into place, the screen transitions to language selection, our first page. Once a user selects a tutorial, our system uses computer vision to analyze their image at each step to check for validity and provides real-time feedback. The following details our tech stack: Frontend React + TypeScript + Tailwind for a mobile-first interface and camera integration Backend Python (FastAPI) for REST API serving verification endpoints and image processing Runpod (GPU inference): Deploying of Siamese neural network endpoint through runpod, reducing inference latency to <100ms for high-volume production use Deploying of Claude Vision API Deploying of Siamese neural network endpoint through runpod, reducing inference latency to <100ms for high-volume production use Deploying of Claude Vision API Supabase for BaaS, database and storage for images of tutorials and repair steps AI & Intelligence MyMemory Translation API for multilingual translation i18next for frontend internationalization and language detection LightWeight On Device Verification: Siamese architecture: REsNet18 backbone pretrained on ImageNet Heavy data augmentation: +-50% brightness/contrast, 180° rotation, random flips CLAHE (Contrast Limited Adaptive Histogram Equalization): Normalizes lighting variations across different environments Comparison: Cosine similarity between embeddings (threshold: 0.81) Center crop (15%): Removes edge noise and focuses on center of assembly 99.8% similarity on matching steps in test Clear rejection (51-67%) on non-matching steps Advanced Model with Feedback: Claude Sonnet 4 Vision API for frame analysis and step verification Gives useful insights about what is wrong with the image (helpful if you're unsure what's wrong) Similar performance to Siamese network Both models have an advantage of not needed large amounts of data, making it easy for people to crowdsource help for repair of medical equipment without investing significant time in curating ML datasets. Integration Zoom API for live expert escalation and support Vercel for frontend deployment and hosting

### Challenges we ran into

Database implementation (expected it to take 5 minutes. Ended up taking waayyyy longer) Zoom API with Render backend. Yeah this one was difficult. It turns out the WiFi was our bottleneck and our request would time out often. Occasionally, some steps would do perform worse than others during the AI analyzation due to varying alignment or lighting Providing support for more languages for translation as each was computationally expensive while maintaining good performance on latency Developing a complex but intuitive user flow (back button, help button, verifications, etc)

### Accomplishments we're proud of

Training a neural network with only 2-3 images per step. Although it seemed impossible, we solved this by choosing a Siamese architecture (learns similarity, not classification) and aggressive data augmentation (25x expansion with rotation, brightness, blur variations). Overcoming Lighting Inconsistency: Early tests failed when images had different lighting than our reference images. We implemented CLAHE preprocessing and trained with ±50% brightness augmentation to help the model be more lighting-invariant.

### What's next

Auto-checks for step completion Rather than manual photo capture, we hope for an accurate CV detection via real-time camera Rather than manual photo capture, we hope for an accurate CV detection via real-time camera AI-created visual instructions Users can view their difficult text-only manuals as step-by-step visual guides. Users can view their difficult text-only manuals as step-by-step visual guides. Medical admin portal A platform for hospitals and manufacturers to upload manuals and convert them into interactive workflows. A platform for hospitals and manufacturers to upload manuals and convert them into interactive workflows. Providing support for more languages Right now, Puzzled supports a wide range of assembly workflows. In the long run, we hope to specialize it in medical equipment repair, advancing our computer vision to detect fine parts and guide important fixes! References: (1): https://www.who.int/data/gho/indicator-metadata-registry/imr-details/4584 (2): https://cdn.who.int/media/docs/default-source/medical-devices/health-technology-management/country-data-on-health-technology-management.pdf?sfvrsn=4c8eefbb_3&utm_source=chatgpt.com (3): https://pubmed.ncbi.nlm.nih.gov/28821280/

## README (from the GitHub repository)

# Puzzled

**40-70% of medical devices in low and middle-income countries are broken.** Not because they're useless, but because technicians lack training and repair guidance in their language.

**Puzzled turns any phone camera into an AI-powered repair assistant** — making manuals make sense, one step at a time.

## How It Works

1. **Select your language** — Supports languages via AI translation
2. **Choose a repair tutorial** — Browse visual guides for medical equipment
3. **Follow step-by-step instructions** — Your camera verifies each step in real-time using AI
4. **Get unstuck instantly** — One tap connects you to a remote expert via Zoom

**The magic:** Our AI learns from just 2-3 images per step (few-shot learning), making it practical to create guides for any device.

## What Makes It Different

### Dual AI Verification
- **Siamese Neural Network** (ResNet18): inference, embeddings, cosine similarity verification
- **Claude Vision API**: Natural language feedback on *why* a step is incorrect
- **CLAHE normalization**: Works in poor lighting conditions (critical for field use)

### Built For The Real World
- **Few-shot learning**: Create new guides with just 2-3 photos per step
- **Multilingual**: Instant translation to 100+ languages via MyMemory API + i18next
- **Expert escalation**: One-tap Zoom integration with Server-to-Server OAuth
- **Mobile-first**: Native camera integration, works on any smartphone

**Stack:** React 19 • TypeScript • Tailwind • FastAPI • PyTorch (ResNet18) • Claude Vision • Supabase • Zoom API

## Quick Start

**Frontend:**
```bash
npm install
npm run dev
# Runs on http://localhost:5173
```

**Backend:**
```bash
cd backend
python3 -m venv venv
source venv/bin/activate  # macOS/Linux (venv\Scripts\activate on Windows)
pip install -r requirements.txt

# Configure environment
cp .env.example .env
# Edit .env with your Zoom API credentials from https://marketplace.zoom.us/develop/create

# Start server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Runs on http://localhost:8000
```

**Test it:** Visit http://localhost:5173, select a tutorial, and try the camera verification!

📚 **Detailed setup, troubleshooting, and development guides:** [backend/README.md](backend/README.md)

## API Reference

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/verify/verify-step` | POST | Verify camera image against reference step |
| `/api/help` | POST | Create instant Zoom meeting with expert |
| `/verify/dashboard` | GET | Real-time verification monitoring dashboard |
| `/docs` | GET | Interactive API documentation |

**Interactive docs:** http://localhost:8000/docs

## Deployment

### Backend (Render)
**Build Command:** `cd backend && pip install -r requirements.txt`
**Start Command:** `cd backend && uvicorn app.main:app --host 0.0.0.0 --port $PORT`

**Required Environment Variables:**
- `ZOOM_ACCOUNT_ID`
- `ZOOM_CLIENT_ID`
- `ZOOM_CLIENT_SECRET`
- `CORS_ORIGINS`
- `ANTHROPIC_API_KEY`

For detailed deployment instructions, see [backend/README.md](backend/README.md)

### Frontend (Vercel/Render Static Site)
**Build Command:** `npm run build`
**Publish Directory:** `dist`

**Environment Variables:**
- `VITE_API_BASE_URL=https://your-backend.onrender.com`
- `VITE_SUPABASE_URL`
- `VITE_SUPABASE_ANON_KEY`

## Built At TreeHacks 2026

**Problem:** Medical devices sit broken in clinics across low and middle-income countries while technicians lack training resources in their language.

**Solution:** AI-powered visual repair guides that work on any phone, in any language, with instant expert backup.

**Impact:** Instead of donating more devices, we help countries fix the ones they already have.

---

**Tech Stack:** React 19 • TypeScript • Tailwind CSS • Vite • FastAPI • PyTorch • ResNet18 • Claude Sonnet 4 • Supabase • Zoom API • i18next • MyMemory Translation API


## Detected evidence (automated analysis)

Indexed codebase: 69 recognized source files, 307 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- PyTorch (technology) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (113 of 113)

```
.DS_Store
.env.example
.gitignore
backend/.env.example
backend/app/__init__.py
backend/app/config.py
backend/app/main.py
backend/app/models/__init__.py
backend/app/models/schemas.py
backend/app/routes/__init__.py
backend/app/routes/help.py
backend/app/routes/verify.py
backend/app/services/__init__.py
backend/app/services/detector.py
backend/app/services/email_service.py
backend/app/services/siamese_detector.py
backend/app/services/zoom_client.py
backend/Dockerfile
backend/Dockerfile.runpod
backend/handler.py
backend/README.md
backend/requirements.txt
backend/siamese_model_new.pth
backend/siamese_model.pth
cv_detector/.DS_Store
cv_detector/new_detector/.DS_Store
cv_detector/new_detector/bbox_annotator.py
cv_detector/new_detector/bbox_extractor.py
cv_detector/new_detector/claude_detector.py
cv_detector/new_detector/cnn_detector.py
cv_detector/new_detector/convert_heic.py
cv_detector/new_detector/convert_tests.py
cv_detector/new_detector/cropped/annotations.json
cv_detector/new_detector/final_verification.py
cv_detector/new_detector/find_best_threshold.py
cv_detector/new_detector/find_optimal_threshold.py
cv_detector/new_detector/lego_model.pth
cv_detector/new_detector/main.py
cv_detector/new_detector/requirements.txt
cv_detector/new_detector/run_tests_final.py
cv_detector/new_detector/run_tests_tuned.py
cv_detector/new_detector/run_tests.py
cv_detector/new_detector/siamese_detector.py
cv_detector/new_detector/siamese_model.pth
cv_detector/new_detector/similarity_detector.py
cv_detector/new_detector/test_normalization.py
cv_detector/new_detector/test_robustness.py
cv_detector/new_detector/test1/.DS_Store
cv_detector/new_detector/test2/.DS_Store
cv_detector/new_detector/verify_final.py
eslint.config.js
index.html
package.json
public/locales/en/camera.json
public/locales/en/common.json
public/locales/en/errors.json
public/locales/en/help.json
public/locales/en/instruction.json
public/locales/en/language.json
public/locales/en/tutorial.json
public/locales/es/camera.json
public/locales/es/common.json
public/locales/es/errors.json
public/locales/es/help.json
public/locales/es/instruction.json
public/locales/es/language.json
public/locales/es/tutorial.json
public/locales/fr/camera.json
public/locales/fr/common.json
public/locales/fr/errors.json
public/locales/fr/help.json
public/locales/fr/instruction.json
public/locales/fr/language.json
public/locales/fr/tutorial.json
README.md
runtime.txt
src/App.css
src/App.tsx
src/components/CameraCapture.tsx
src/components/HelpButton.tsx
src/components/InstructionStepper.tsx
src/components/LanguageSelector.tsx
src/components/PageHeader.tsx
src/i18n.ts
src/index.css
src/main.tsx
src/pages/AllDone.tsx
src/pages/Analyzing.tsx
src/pages/Camera.tsx
src/pages/CameraStepCompletion.tsx
src/pages/Help.tsx
src/pages/Instruction.tsx
src/pages/Landing.tsx
src/pages/Language.tsx
src/pages/Preview.tsx
src/pages/SetupCamera.tsx
src/pages/Tutorial.tsx
src/pages/Verified.tsx
src/pages/Zoom.tsx
src/services/api.ts
src/services/supabase.ts
src/services/tutorialService.ts
src/types/api.ts
src/types/database.ts
src/types/HelpButton.tsx
src/utils/translateContent.ts
src/utils/translationCache.ts
test_verification.py
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vercel.json
vite.config.ts
```

### Dependencies

- backend/requirements.txt: aiosmtplib@==3.0.2, anthropic@>=0.18.0, fastapi@==0.115.0, httpx@==0.27.0, numpy@>=1.24.0, opencv-python-headless@>=4.8.0, pillow@>=9.0.0, pydantic@==2.9.2, pydantic-settings@==2.5.2, python-dotenv@==1.0.1, python-multipart@>=0.0.6, uvicorn[standard]@==0.30.0
- cv_detector/new_detector/requirements.txt: anthropic@>=0.18.0, numpy@>=1.24.0, opencv-python@>=4.8.0, pillow@>=10.0.0, pillow-heif@>=0.13.0, torch@>=2.0.0, torchvision@>=0.15.0
- package.json: @eslint/js@^9.39.1, @heroicons/react@^2.2.0, @supabase/supabase-js@^2.95.3, @tailwindcss/vite@^4.1.18, @types/node@^24.10.1, @types/react@^19.2.7, @types/react-dom@^19.2.3, @types/react-webcam@^1.1.0, @vitejs/plugin-react@^5.1.1, eslint@^9.39.1, eslint-plugin-react-hooks@^7.0.1, eslint-plugin-react-refresh@^0.4.24, flag-icons@^7.5.0, globals@^16.5.0, i18next@^25.8.7, i18next-browser-languagedetector@^8.2.1, i18next-http-backend@^3.0.2, react@^19.2.0, react-dom@^19.2.0, react-i18next@^16.5.4, react-router-dom@^7.13.0, react-webcam@^7.2.0, tailwindcss@^4.1.18, typescript@~5.9.3, typescript-eslint@^8.48.0, vite@^7.3.1

### Recent commits (newest first)

- add video
- edit readme
- Add help to failed verfication
- minor change for index.html
- Update preview help button
- change index.html
- move runtime.txt to root + favicon
- minor tweak
- Add help button after failed verfication
- Fix help
- Add logo to landing
- Edit landing
- Fix progress bar, add logo
- Fix continue to next step
- specify python 3.11 for render deployment
- remove verified endpoint
- Fix verfication preview
- Add start_url to email notifications for Zoom meetings
- Fix try again
- Update camera steps flow

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

### package.json

```
{
  "name": "treehacks26",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@heroicons/react": "^2.2.0",
    "@supabase/supabase-js": "^2.95.3",
    "@types/react-webcam": "^1.1.0",
    "flag-icons": "^7.5.0",
    "i18next": "^25.8.7",
    "i18next-browser-languagedetector": "^8.2.1",
    "i18next-http-backend": "^3.0.2",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "react-i18next": "^16.5.4",
    "react-router-dom": "^7.13.0",
    "react-webcam": "^7.2.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.1",
    "@tailwindcss/vite": "^4.1.18",
    "@types/node": "^24.10.1",
    "@types/react": "^19.2.7",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^5.1.1",
    "eslint": "^9.39.1",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.24",
    "globals": "^16.5.0",
    "tailwindcss": "^4.1.18",
    "typescript": "~5.9.3",
    "typescript-eslint": "^8.48.0",
    "vite": "^7.3.1"
  }
}

```

### backend/requirements.txt

```
fastapi==0.115.0
uvicorn[standard]==0.30.0
pydantic==2.9.2
pydantic-settings==2.5.2
httpx==0.27.0
python-dotenv==1.0.1
anthropic>=0.18.0
python-multipart>=0.0.6
aiosmtplib==3.0.2
opencv-python-headless>=4.8.0
numpy>=1.24.0
pillow>=9.0.0
```

### backend/Dockerfile

```
FROM python:3.11-slim

WORKDIR /app

# Install system dependencies (including OpenCV deps)
RUN apt-get update && apt-get install -y \
    gcc \
    libgl1 \
    libglib2.0-0 \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements first for caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY app/ ./app/
COPY reference_images/ ./reference_images/

# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app

# Expose port for local testing
EXPOSE 8000

# Default command (FastAPI server)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

```

### cv_detector/new_detector/requirements.txt

```
# Core dependencies
opencv-python>=4.8.0
numpy>=1.24.0
pillow>=10.0.0
pillow-heif>=0.13.0

# For CNN model
torch>=2.0.0
torchvision>=0.15.0

# For Claude API
anthropic>=0.18.0

```

### src/main.tsx

```typescript
import { StrictMode, Suspense } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import './i18n' // Initialize i18n
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <Suspense fallback={<div>Loading...</div>}>
      <App />
    </Suspense>
  </StrictMode>,
)

```

### src/App.tsx

```typescript
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Analyzing from "./pages/Analyzing";
import CameraStepCompletion from "./pages/CameraStepCompletion";
import Instruction from "./pages/Instruction";
import Landing from "./pages/Landing.tsx";
import Language from "./pages/Language";
import Preview from "./pages/Preview";
import SetupCamera from "./pages/SetupCamera";
import Tutorial from "./pages/Tutorial";
import Verified from "./pages/Verified";
import Zoom from "./pages/Zoom";
import Help from "./pages/Help";
import AllDone from "./pages/AllDone";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Landing />} />
        <Route path="/landing" element={<Landing />} />
        <Route path="/language" element={<Language />} />
        <Route path="/tutorial" element={<Tutorial />} />
        <Route path="/camerasetup/:tutorialId" element={<SetupCamera />} />
        <Route path="/instruction/:tutorialId" element={<Instruction />} />
        <Route path="/camera-step-completion/:tutorialId" element={<CameraStepCompletion />} />
        <Route path="/preview/:tutorialId" element={<Preview />} />
        <Route path="/zoom" element={<Zoom />} />
        <Route path="/analyzing" element={<Analyzing />} />
        <Route path="/alldone" element={<AllDone/>} />
        <Route path="/verified/:tutorialId" element={<Verified />} />
        <Route path="/help/:tutorialId" element={<Help />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

```

### backend/app/main.py

```python
"""
FastAPI Application Entry Point
Main application with CORS configuration and route registration
"""

import logging

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.config import get_settings
from app.routes import help, verify

# Configure logging
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

settings = get_settings()

app = FastAPI(
    title="TreeHacks26 API",
    description="Backend API for Hackathon Help System with Zoom Integration",
    version="1.0.0",
)

# CORS Configuration - allow all origins for hackathon
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Include routers
app.include_router(help.router)
app.include_router(verify.router)


@app.get("/health")
async def health_check():
    """
    Health check endpoint for monitoring and deployment verification.

    Returns:
        dict: Health status and environment information
    """
    return {"status": "healthy", "environment": settings.environment}


@app.get("/")
async def root():
    """
    Root endpoint with API information.

    Returns:
        dict: Welcome message and available endpoints
    """
    return {
        "message": "TreeHacks26 API - Hackathon Help System",
        "docs": "/docs",
        "health": "/health",
        "dashboard": "/verify/dashboard",
    }

```

### cv_detector/new_detector/main.py

```python
"""
Lego Assembly Verification App
Main entry point supporting Claude API, CNN, and similarity-based verification.
"""

import argparse
import cv2
from pathlib import Path


def main():
    parser = argparse.ArgumentParser(
        description="Lego Assembly Step Verification",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Run with Claude API (webcam):
  python main.py --mode claude --api-key YOUR_KEY

  # Run with Claude API (manual image input):
  python main.py --mode claude --api-key YOUR_KEY --manual

  # Verify a single image with Claude:
  python main.py --mode claude --api-key YOUR_KEY --image test.jpg --step 3

  # Run similarity-based (no training needed):
  python main.py --mode similarity

  # Run with CNN model:
  python main.py --mode cnn --train
        """
    )

    parser.add_argument(
        "--mode", "-m",
        choices=["claude", "cnn", "similarity"],
        default="claude",
        help="Verification mode: 'claude' (API), 'cnn' (trained model), or 'similarity' (no training)"
    )
    parser.add_argument(
        "--api-key", "-k",
        help="Anthropic API key (required for claude mode)"
    )
    parser.add_argument(
        "--reference", "-r",
        default="./steps_jpg",
        help="Directory containing reference step images"
    )
    parser.add_argument(
        "--model",
        default="lego_model.pth",
        help="Path to CNN model file"
    )
    parser.add_argument(
        "--train", "-t",
        action="store_true",
        help="Train the CNN model before running"
    )
    parser.add_argument(
        "--epochs", "-e",
        type=int,
        default=50,
        help="Number of training epochs"
    )
    parser.add_argument(
        "--camera", "-c",
        type=int,
        default=0,
        help="Camera index for live capture"
    )
    parser.add_argument(
        "--image", "-i",
        help="Single image file to verify (instead of camera)"
    )
    parser.add_argument(
        "--step", "-s",
        type=int,
        default=1,
        help="Step number to verify against (for single image mode)"
    )
    parser.add_argument(
        "--cropped", "-x",
        default="./cropped",
        help="Directory containing manually cropped reference images"
    )
    parser.add_argument(
        "--threshold",
        type=float,
        default=0.75,
        help="Similarity threshold for similarity mode (0-1)"
    )
    parser.add_argument(
        "--manual",
        action="store_true",
        help="Manual image input mode (type image paths instead of webcam)"
    )

    args = parser.parse_args()

    # Validate reference directory
    ref_path = Path(args.reference)
    if not ref_path.exists():
        print(f"Error: Reference directory not found: {args.reference}")
        print("Run convert_heic.py first to create JPG images.")
        return 1

    # Count available steps
    steps = set()
    for f in ref_path.glob("s*-*.jpg"):
        step_num = int(f.stem.split("-")[0][1:])
        steps.add(step_num)

    if not steps:
        print(f"Error: No step images found in {args.reference}")
        print("Expected format: s1-0.jpg, s1-1.jpg, s2-0.jpg, etc.")
        return 1

    print(f"\nLego Assembly Verification")
    print(f"=" * 40)
    print(f"Mode: {args.mode.upper()}")
    print(f"Reference directory: {args.reference}")
    print(f"Steps available: {len(steps)} (s1 to s{max(steps)})")
    print(f"Total images: {len(list(ref_path.glob('s*-*.jpg')))}")

    # Check for cropped images
    cropped_path = Path(args.cropped)
    if cropped_path.exists():
        cropped_count = len(list(cropped_path.glob("cropped_*.jpg")))
        print(f"Cropped images: {cropped_count}")
    else:
        cropped_path = None
    print()

    if args.mode == "claude":
        if not args.api_key:
            print("Error: --api-key required for Claude mode")
            return 1

        from claude_detector import ClaudeLegoDetector, LegoAssemblyApp, verify_from_file

        if args.image:
            # Single image verification
            verify_from_file(args.api_key, args.reference, args.image, args.step)
        elif args.manual:
            # Manual image input mode
            run_manual_claude(args.api_key, args.reference, str(cropped_path) if cropped_path else None)
        else:
            # Webcam mode
            app = LegoAssemblyApp(
                args.api_key, args.reference, args.camera,
                str(cropped_path) if cropped_path else None
            )
            app.run_interactive()

    elif args.mode == "similarity":
        from similarity_detector import SimilarityDetector, SimilarityAssemblyApp

        if args.image:
            detector = SimilarityDetector(
                reference_dir=args.reference,
                cropped_dir=str(cropped_path) if cropped_path else None,
                similarity_threshold=args.threshold
            )
            image = cv2.imread(args.image)
            if image is None:
                print(f"Error: Could not read {args.image}")
                return 1

            result = detector.verify_step(image, args.step)
            print(f"\nVerification Result:")
            print(f"  Step: {result.step}")
            print(f"  Similarity: {result.similarity:.2%}")
            print(f"  Match: {result.is_match}")
            print(f"  {result.explanation}")
        else:
            app = SimilarityAssemblyApp(
                reference_dir=args.reference,
                cropped_dir=str(cropped_path) if cropped_path else None,
                camera_index=args.camera,
                threshold=args.threshold
            )
            app.run_interactive()

    else:  # CNN mode
        from cnn_detector import CNNLegoDetector, CNNLegoAssemblyApp

        if args.train:
            print("Training CNN model...")
            detector = CNNLegoDetector(reference_dir=args.reference)
            detector.train(epochs=a
[truncated — 4086 more characters]
```

### vite.config.ts

```typescript
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
})

```

### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/puzzled.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Puzzled</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

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