# Project export: Dawned

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: Cal Hacks 12.0
- Tagline: Stop cramming! Dawned is the web app that uses spaced repetition with quizzes, flashcards, and a unique student chatbot you tutor. Turn "I used to know this" into "I've mastered this."
- Devpost: https://devpost.com/software/dawned
- GitHub: https://github.com/ktagdiwala/calhacks12
- Video: https://www.youtube.com/embed/p-znfLSAjPM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — ninaw04 (14 commits), Viha Shah (10 commits), Krishna T (3 commits), samfvv (1 commits)

## Devpost submission (written by the team)

### Inspiration

We’ve all had that “I used to know this” moment, whether it’s preparing for an interview, helping out a friend with homework, or revisiting a college topic we once mastered. Knowledge fades quickly without repetition, yet existing tools like flashcard apps don’t adapt well to richer learning media like lecture videos, articles, handwritten notes, PPTs, PDFs, and more. Dawned was born out of the desire to make knowledge retention effortless. It combines spaced repetition, AI-generated study content, and playful, periodic reminders that keep your learning “fresh,” long after you’ve finished a course.

### What it does

Dawned helps you retain what you’ve already learned through smart, periodic micro-reviews. User Input: Upload your notes, PDFs, images, articles, or lecture videos into Tags (topics). Behind the scenes, an AI extracts and summarizes key ideas to create flashcards, quiz questions, and discussion prompts. User Interaction: Each day, Dawned sends you a short “challenge,” which includes a mix of flashcards, quiz questions, and a “tutor-a-student” chat where you teach an AI student. Your confidence ratings and explanations drive a spaced repetition algorithm that adapts to your progress. Analytics: A beautiful dashboard visualizes your mastery over time using a time-of-day motif, from twilight (foggy memory) to dawn (concept clarity). Weekly digests summarize how your knowledge is “dawning” across tags.

### How we built it

Frontend: Built with Vite, React, and TailwindCSS for fast, responsive, and elegant UI/UX. Backend: Node.js + Express REST API connected to a Supabase PostgreSQL database via Prisma ORM. Database Schema: Structured around User, Tag, QuizQuestion, Flashcard, Log, and UserInput models for seamless tracking of spaced repetition intervals and user progress. AI Generation: AI agents analyze uploaded materials (PDFs, text, YouTube links, etc) to auto-generate flashcards, quizzes, and discussion prompts using LLM-based question generation. Analytics: Data visualized as twilight-to-dawn gradients, representing the user’s growing retention.

### Challenges we ran into

Inconsistent WiFi connectivity Establishing a persistent database connection Integrating the frontend, backend, and middleware to work seamlessly Processing multiple input types (images, PDFs, YouTube videos, etc) and normalizing them into structured text for question generation. Building a cohesive experience across AI generation, spaced repetition, and analytics to ensure the system feels intelligent, not random.

### Accomplishments we're proud of

Built a full end-to-end spaced repetition learning assistant within a weekend. Seamlessly connected Letta AI content generation Created an engaging visual motif (“twilight to dawn”) that turns analytics into a metaphor for learning progress. Designed an expandable system where users can plug in any learning material and immediately get actionable quizzes.

### What we learned

How to design a schema that supports temporal learning (tracking “next review dates” and retention intervals). The power of combining AI retrieval, question generation, and reinforcement in one feedback loop. How important UX metaphors (like “dawn”) are for motivation and retention.

### What's next

Mobile App + Push Notifications: Bring the periodic “Dawned challenge” to your phone for true habit formation. Multi-modal AI Ingestion: Support lecture audio, screenshots, and course slide decks. Social Learning: Compare your “dawn levels” with friends or study groups. Open Education Integration: Create a browser extension to process learning materials directly from a university's LMS (such as Canvas) Automation: Integrated Postman Flows and Monitors to schedule daily review runs and weekly digest emails — turning Postman into a no-cron “brain” for our spaced repetition system. Email Notifications: Automated via Composio Tool router to deliver personalized “daily dawn” challenges and digest summaries. Here is a bonus content video: https://youtu.be/BUN0QlzG9MA

## README (from the GitHub repository)

# calhacks12



## Detected evidence (automated analysis)

Indexed codebase: 57 recognized source files, 197 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — 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
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (72 of 72)

```
.DS_Store
.gitignore
express/.DS_Store
express/backend/.DS_Store
express/backend/.gitignore
express/backend/app.js
express/backend/backendreadme.md
express/backend/bin/www
express/backend/config/config.json
express/backend/config/error.js
express/backend/config/routes.js
express/backend/config/utilities.js
express/backend/lib/prisma.js
express/backend/lib/supabase.js
express/backend/middleware/auth.js
express/backend/package.json
express/backend/prisma.config.ts.backup
express/backend/prisma/schema.prisma
express/backend/prisma/seed.js
express/backend/routes/ai_endpoints.js
express/backend/routes/analytics.js
express/backend/routes/APIreadme.md
express/backend/routes/flashcards.js
express/backend/routes/index.js
express/backend/routes/tags.js
frontend/.gitignore
frontend/components/DashboardPage.tsx
frontend/components/Flashcard.tsx
frontend/components/FlashcardsPage.tsx
frontend/components/LoginPage.tsx
frontend/components/Quiz.tsx
frontend/components/QuizPage.tsx
frontend/components/Sidebar.tsx
frontend/components/TopicsPage.tsx
frontend/components/TutorStudent.tsx
frontend/components/TutorStudentPage.tsx
frontend/components/ui/alert.tsx
frontend/components/ui/avatar.tsx
frontend/components/ui/badge.tsx
frontend/components/ui/button.tsx
frontend/components/ui/card.tsx
frontend/components/ui/dialogue.tsx
frontend/components/ui/input.tsx
frontend/components/ui/label.tsx
frontend/components/ui/progress.tsx
frontend/components/ui/radio-group.tsx
frontend/components/ui/scroll-area.tsx
frontend/components/ui/select.tsx
frontend/components/ui/tabs.tsx
frontend/components/ui/textarea.tsx
frontend/components/ui/utils.ts
frontend/components/UploadContent.tsx
frontend/eslint.config.js
frontend/index.html
frontend/lib/supabase.ts
frontend/package.json
frontend/postcss.config.cjs
frontend/README.md
frontend/services/api.ts
frontend/src/App.css
frontend/src/App.tsx
frontend/src/index.css
frontend/src/lib/supabase.ts
frontend/src/main.jsx
frontend/src/services/api.ts
frontend/tailwind.config.ts
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.js
README.md
SUPABASE_AUTH_SETUP.md
TAGS_API.md
```

### Dependencies

- express/backend/package.json: @prisma/client@^6.18.0, @supabase/supabase-js@^2.76.1, bcryptjs@^2.4.3, cookie-parser@~1.4.4, cors@^2.8.5, debug@~2.6.9, dotenv@^17.2.3, express@~4.16.1, http-errors@~1.6.3, morgan@~1.9.1, prisma@^6.18.0
- frontend/package.json: @eslint/js@^9.36.0, @letta-ai/letta-client@^0.0.68665, @radix-ui/react-avatar@^1.1.10, @radix-ui/react-dialog@^1.1.15, @radix-ui/react-label@^2.1.7, @radix-ui/react-progress@^1.1.2, @radix-ui/react-radio-group@^1.3.8, @radix-ui/react-scroll-area@^1.2.10, @radix-ui/react-select@^2.2.6, @radix-ui/react-tabs@^1.1.13, @supabase/supabase-js@^2.76.1, @tailwindcss/postcss@^4.1.16, @types/react@^19.1.16, @types/react-dom@^19.1.9, @vitejs/plugin-react@^5.0.4, autoprefixer@^10.4.21, axios@^1.12.2, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9.36.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.22, globals@^16.4.0, lucide-react@^0.548.0, react@^19.1.1, react-dom@^19.1.1, tailwind-merge@^3.3.1, tailwindcss@^4.1.16, vite@^7.1.7

### Recent commits (newest first)

- Merge pull request #28 from ktagdiwala/small-fixes
- Merge branch 'main' into small-fixes
- Post ai response (#27)
- :explosion: analytics page yippee (#26)
- Updated api routers (#25)
- Updated api routers (#24)
- :sparkles: quick fixes
- Any user (#23)
- Added a seeding script to prepopulate the database with sample data (#22)
- Merge pull request #21 from ktagdiwala/frontend
- Merge branch 'main' into frontend
- :sparkles: merging frontend mockups into main
- :lipstick: all core UI mockups
- :sparkles: topics and uploading content mockup
- Frontend (#20)
- merge conflicts
- sign up works
- progress?
- Merge pull request #19 from ktagdiwala/frontend
- :sparkles: mockup for login and signup page

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

### TAGS_API.md

```markdown
# 📌 Tags API Endpoints

## Database Schema Changes

Added `userId` field to the `Tag` model to associate tags with specific users.

```prisma
model Tag {
  id          Int      @id @default(autoincrement())
  name        String   @unique
  description String?

  // Foreign keys
  userId      Int

  // Relations
  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  quizQuestions QuizQuestion[]
  flashcards    Flashcard[]
  logs          Log[]

  @@map("tags")
}
```

## API Endpoints

### 1. GET All Tags (Global)

**Endpoint:** `GET /tags`

**Authentication:** Not required (Public)

**Description:** Get all tags across all users

**Response:**

```json
[
  {
    "id": 1,
    "name": "Biology",
    "description": "Biology topics",
    "userId": 1,
    "quizQuestions": [],
    "flashcards": []
  }
]
```

---

### 2. GET All Tags for a User

**Endpoint:** `GET /tags/user/:userId`

**Authentication:** Required (Protected)

**Description:** Get all tags created by a specific user

**Parameters:**

- `userId` (path) - The ID of the user

**Authorization:** User can only access their own tags

**Response:**

```json
[
  {
    "id": 1,
    "name": "Biology",
    "description": "Biology topics",
    "userId": 1,
    "quizQuestions": [],
    "flashcards": []
  },
  {
    "id": 2,
    "name": "Chemistry",
    "description": null,
    "userId": 1,
    "quizQuestions": [],
    "flashcards": []
  }
]
```

---

### 3. POST New Tag (Current User)

**Endpoint:** `POST /tags`

**Authentication:** Required (Protected)

**Description:** Create a new tag for the authenticated user

**Body:**

```json
{
  "name": "Biology",
  "description": "Biology topics"
}
```

**Validation:**

- `name` is required
- Tag name must be unique for the user
- `description` is optional

**Response (201 Created):**

```json
{
  "message": "Tag created successfully",
  "tag": {
    "id": 1,
    "name": "Biology",
    "description": "Biology topics",
    "userId": 1,
    "quizQuestions": [],
    "flashcards": []
  }
}
```

---

### 4. POST New Tag for Specific User

**Endpoint:** `POST /tags/:userId`

**Authentication:** Required (Protected)

**Description:** Create a new tag for a specific user

**Parameters:**

- `userId` (path) - The ID of the user

**Body:**

```json
{
  "name": "Chemistry",
  "description": "Chemistry topics"
}
```

**Authorization:** User can only create tags for themselves

**Validation:**

- `name` is required
- Tag name must be unique per user
- `description` is optional

**Response (201 Created):**

```json
{
  "message": "Tag created successfully",
  "tag": {
    "id": 2,
    "name": "Chemistry",
    "description": "Chemistry topics",
    "userId": 1,
    "quizQuestions": [],
    "flashcards": []
  }
}
```

---

## Error Responses

### 400 Bad Request

```json
{
  "error": "Tag name is required"
}
```

### 403 Forbidden

```json
{
  "error": "You can only access your own tags"
}
```

### 400 Conflict

```json
{
  "error": "Tag 
[truncated — 770 more characters]
```

### SUPABASE_AUTH_SETUP.md

```markdown
# Supabase Auth Integration - Setup Complete ✅

## What Was Set Up

### Backend (Express)

#### 1. **Auth Middleware** (`/middleware/auth.js`)

- Validates Supabase JWT tokens from Authorization headers
- Attaches user info to `req.user` for protected routes
- Returns 401 if token is invalid or missing

#### 2. **Auth Routes** (`/routes/index.js`)

**Public Endpoints:**

- `POST /auth/signup` - Create new user account
- `POST /auth/signin` - Sign in with email/password
- `GET /` - Health check

**Protected Endpoints (require JWT token):**

- `POST /auth/signout` - Sign out
- `GET /auth/me` - Get current authenticated user
- `GET /users` - Get all users
- `GET /users/:id` - Get user by ID
- `POST /users` - Create new user (database)

### Frontend (React)

#### 1. **Supabase Client** (`/src/lib/supabase.ts`)

- Initializes Supabase client with your credentials
- Exported for use throughout the app

#### 2. **API Service** (`/src/services/api.ts`)

- Axios instance with automatic JWT token injection
- Exports `authAPI` functions: signup, signin, signout, getCurrentUser
- Exports `userAPI` functions: getAllUsers, getUserById, createUser
- All requests automatically include Bearer token in Authorization header

#### 3. **Updated LoginPage** (`/components/LoginPage.tsx`)

- Integrated Supabase auth for sign up and sign in
- Error/success messages displayed
- Loading states
- Form validation (password matching, minimum length)
- Calls backend API for authentication

#### 4. **Environment Variables** (`/.env`)

```
REACT_APP_SUPABASE_URL=https://jhmivjbrimzssbmnwvda.supabase.co
REACT_APP_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
REACT_APP_API_URL=http://localhost:3000
```

## How It Works

### Sign Up Flow

1. User enters email, password, and name in LoginPage
2. `handleSignup` calls `authAPI.signup()`
3. API request is sent to `POST /auth/signup`
4. Backend creates user in Supabase Auth + Prisma database
5. Session token is returned
6. User is logged in automatically

### Sign In Flow

1. User enters email and password
2. `handleLogin` calls `authAPI.signin()`
3. API request sent to `POST /auth/signin`
4. Backend validates credentials with Supabase
5. User data from database is returned
6. `onLogin` callback is called with user info

### Protected Route Flow

1. React makes request with `axios` instance
2. Interceptor adds `Authorization: Bearer <token>` header
3. Express validates token with `authenticateUser` middleware
4. If valid, `req.user` is set with decoded user info
5. Route handler proceeds; if invalid, 401 is returned

## Required Packages

### Backend

```bash
npm install @supabase/supabase-js
```

### Frontend

```bash
npm install axios @supabase/supabase-js
```

## Testing

### Test Sign Up

```bash
curl -X POST http://localhost:3000/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "password123",
    "username": "testuser"
  }'
```

### Test Sign In

```bash
curl
[truncated — 1010 more characters]
```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@letta-ai/letta-client": "^0.0.68665",
    "@radix-ui/react-avatar": "^1.1.10",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-label": "^2.1.7",
    "@radix-ui/react-progress": "^1.1.2",
    "@radix-ui/react-radio-group": "^1.3.8",
    "@radix-ui/react-scroll-area": "^1.2.10",
    "@radix-ui/react-select": "^2.2.6",
    "@radix-ui/react-tabs": "^1.1.13",
    "@supabase/supabase-js": "^2.76.1",
    "@tailwindcss/postcss": "^4.1.16",
    "axios": "^1.12.2",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.548.0",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "tailwind-merge": "^3.3.1",
    "tailwindcss": "^4.1.16"
  },
  "devDependencies": {
    "@eslint/js": "^9.36.0",
    "@types/react": "^19.1.16",
    "@types/react-dom": "^19.1.9",
    "@vitejs/plugin-react": "^5.0.4",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.36.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.22",
    "globals": "^16.4.0",
    "vite": "^7.1.7"
  }
}

```

### express/backend/package.json

```
{
  "name": "backend",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "dev": "node ./bin/www",
    "seed": "node prisma/seed.js"
  },
  "dependencies": {
    "@prisma/client": "^6.18.0",
    "@supabase/supabase-js": "^2.76.1",
    "bcryptjs": "^2.4.3",
    "cookie-parser": "~1.4.4",
    "cors": "^2.8.5",
    "debug": "~2.6.9",
    "dotenv": "^17.2.3",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1"
  },
  "devDependencies": {
    "prisma": "^6.18.0"
  }
}

```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### express/backend/app.js

```javascript
require("dotenv").config();

var express = require("express");
var cors = require("cors");
var errorConfig = require("./config/error");
var utilitiesConfig = require("./config/utilities");
var routesConfig = require("./config/routes");

var app = express();

// Enable CORS for React frontend
app.use(cors());

utilitiesConfig(app);
routesConfig(app);
errorConfig(app);

module.exports = app;

```

### frontend/src/App.tsx

```typescript
import { useState } from 'react';
import { Sidebar } from '../components/Sidebar';
import { TopicsPage } from '../components/TopicsPage';
import { UploadContent } from '../components/UploadContent';
import { FlashcardsPage } from '../components/FlashcardsPage'
import { QuizPage } from '../components/QuizPage';
import { TutorStudentPage } from '../components/TutorStudentPage';
import { DashboardPage } from '../components/DashboardPage';
import { LoginPage } from '../components/LoginPage';
import { ProfilePage } from './components/ProfilePage';

export type PageType = 'dashboard' | 'topics' | 'upload' | 'flashcards' | 'quiz' | 'tutor' | 'profile';

interface User {
  name: string;
  email: string;
}

export default function App() {
  const [currentPage, setCurrentPage] = useState<PageType>('dashboard');
  const [user, setUser] = useState<User | null>(null);

  const handleLogin = (userData: User) => {
    setUser(userData);
  };

  const handleLogout = () => {
    setUser(null);
    setCurrentPage('dashboard');
  };

  if (!user) {
    return <LoginPage onLogin={handleLogin} />;
  }

  const renderPage = () => {
    switch (currentPage) {
      case 'dashboard':
        return <DashboardPage />;
      case 'topics':
        return <TopicsPage />;
      case 'upload':
        return <UploadContent />;
      case 'flashcards':
        return <FlashcardsPage />;
      case 'quiz':
        return <QuizPage />;
      case 'tutor':
        return <TutorStudentPage />;
      // case 'profile':
      //   return <ProfilePage user={user} onLogout={handleLogout} />;
      default:
        return <DashboardPage />;
    }
  };

  return (
    <div className="flex h-screen bg-slate-50">
      <Sidebar currentPage={currentPage} onPageChange={setCurrentPage} user={user} />
      <main className="flex-1 overflow-y-auto">
        {renderPage()}
      </main>
    </div>
  );
}

```

### express/backend/routes/index.js

```javascript
var express = require("express");
var router = express.Router();
const prisma = require("../lib/prisma");
const supabase = require("../lib/supabase");
const { authenticateUser } = require("../middleware/auth");

/* Health check endpoint - PUBLIC */
router.get("/", (req, res) => {
  res.json({ status: "ok", message: "API is running" });
});

/* SIGN UP - PUBLIC - DEMO MODE */
router.post("/auth/signup", async (req, res, next) => {
  try {
    const { email, password } = req.body;

    if (!email) {
      return res.status(400).json({ error: "Email is required" });
    }

    // DEMO MODE: Create user directly in database
    // No Supabase Auth required for demo
    const user = await prisma.user.create({
      data: {
        email,
        username: email.split("@")[0], // Use email prefix as username
        password: password || "demo_password", // Use provided password or default
        authId: `demo_${email}`, // Generate a fake authId
      },
    });

    res.status(201).json({
      message: "User created successfully",
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
      },
    });
  } catch (error) {
    if (error.code === "P2002") {
      return res.status(400).json({ error: "Email already exists" });
    }
    next(error);
  }
});

/* SIGN IN - PUBLIC - DEMO MODE: ANY VALID EMAIL */
router.post("/auth/signin", async (req, res, next) => {
  try {
    console.log("POST /auth/signin - Request body:", req.body);
    const { email, password } = req.body;

    if (!email) {
      return res.status(400).json({ error: "Email is required" });
    }


    const user = await prisma.user
      .findUnique({
        where: { email: email },
      })
      .catch((err) => {
        console.error("Database query error:", err);
        throw err;
      });

    if (!user) {
      return res
        .status(401)
        .json({ error: "User not found. Please sign up first." });
    }

    // DEMO MODE: Skip password validation, just return the user
    // In production, validate password against Supabase
    return res.json({
      message: "Signed in successfully",
      session: null, // No real session in demo mode
      user,
    });
  } catch (error) {
    console.error("Full error:", error);
    next(error);
  }
}); /* SIGN OUT - PROTECTED */
router.post("/auth/signout", authenticateUser, async (req, res, next) => {
  try {
    res.json({ message: "Signed out successfully" });
  } catch (error) {
    next(error);
  }
});

/* GET current authenticated user - PROTECTED */
router.get("/auth/me", authenticateUser, async (req, res, next) => {
  try {
    const user = await prisma.user.findUnique({
      where: { authId: req.user.id },
      include: {
        logs: true,
        userInputs: true,
      },
    });

    if (!user) {
      return res.status(404).json({ error: "User not found in database" });
    }

    res.json(user);
  } catch (error) {
    next(error);
  }
});

/* GET all users - PROTECTED */
router.get("/users", authenticateUser, async (req, res, next) => {
  try {
    const users = await prisma.user.findMany({
      include: {
        logs: true,
        userInputs: true,
      },
    });
    res.json(users);
  } catch (error) {
    next(error);
  }
});

/* GET user by ID - PROTECTED */
router.get("/users/:id", authenticateUser, async (req, res, next) => {
  try {
    const user = await prisma.user.findUnique({
      where: { id: parseInt(req.params.id) },
      include: {
        logs: true,
        userInputs: true,
      },
    });

    if (!user) {
      return res.status(404).json({ error: "User not found" });
    }

    res.json(user);
  } catch (error) {
    next(error);
  }
});

/* CREATE a new user - PROTECTED */
router.post("/users", authenticateUser, async (req, res, next) => {
  try {
    const { email, username, authId } = req.body;

    const user = await prisma.user.create({
      data: {
        email,
        username,
        password: "supabase_managed",
        authId,
      },
    });

    res.status(201).json(user);
  } catch (error) {
    next(error);
  }
});

module.exports = router;

```

### frontend/vite.config.js

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

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

```

### frontend/index.html

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

```

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