# Project export: Daily1600 (SAT Prep) app

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: OpenAI Build Week
- Tagline: Daily1600 makes SAT prep addictive. It builds strong study habits through daily streaks, an AI Socratic Coach that guides deep reasoning, and a parent insight portal to keep families connected.
- Devpost: https://devpost.com/software/daily1600-sat-prep-app
- GitHub: https://github.com/klee718/Daily1600
- Demo: https://daily1600.vercel.app/
- Video: https://www.youtube.com/embed/MBnbT7vDm6I?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — klee718 (4 commits)

## Devpost submission (written by the team)

### Inspiration

The story of Daily1600 starts at the kitchen table. My 15-year-old son Jake is preparing for the Digital SAT. Like millions of families across America, we quickly ran into two walls that most parents don't talk about openly: the cost barrier and the burnout barrier. Private SAT tutoring in our area runs $150 to $300 per hour. A competitive 10-week program easily clears $3,000 to $5,000 - a number that puts elite preparation out of reach for most families. And for the families who can afford it? We watched Jake disengage anyway. Traditional prep is passive, repetitive, and deeply unrewarding. He'd close the prep book after 20 minutes and pick up his phone. That phone is where the lightbulb hit us. Jake had been using a habit-forming language-learning app for years, maintaining a 400+ day streak in Spanish with zero external pressure from us. We watched the same kid who couldn't sit through 20 minutes of SAT drills spend an hour gladly answering vocabulary questions because he didn't want to lose his streak. The insight was obvious once we saw it: the mechanics are the same. Both language learning and SAT prep require daily repetition, incremental mastery, and long-term habit formation. The best habit-forming apps had already cracked the code on making that loop feel like play instead of punishment. Daily1600 is our answer: take the gamification DNA that makes those apps genuinely habit-forming - streaks, hearts, gem economies, power-up shops, milestone roadmaps - and rebuild it entirely around the Digital SAT. Then power the whole thing with an AI tutor that is both infinitely patient and strategically withholding, guiding students to their own breakthroughs rather than just handing them answer keys. Our goal: a perfect 1600 should feel earnable, not just purchasable.

### What it does

Daily1600 turns SAT preparation into an engaging, structured daily ritual built around five core systems: 1. The Daily Study Loop Students answer high-yield, Digital SAT-style questions curated to their chosen track (Math or English/Reading & Writing). Questions are sequenced by cognitive difficulty and surface area vulnerability - students aren't just drilling randomly; they're working a personalized syllabus. 2. The Gamification Engine We implement a full habit-forming reward loop: Daily Streaks - consecutive-day tracking with visual feedback and milestone unlocks Heart System - limited "lives" per session that create constructive stakes Gem Economy - earned through correct answers and streak milestones Power-Up Shop - spend Gems on Streak Freezes (protect streaks during sick days) and Heart Refills Double-or-Nothing Gem Wagers - optional high-risk, high-reward daily challenge mode 3. The Border Collie Tutor This is where OpenAI's GPT-5.6 becomes the heart of the product. When a student answers incorrectly, they don't get an answer - they get a teacher. The Border Collie Tutor is engineered with a carefully tuned system prompt that enforces a strict Socratic pedagogy: it identifies exactly which reasoning step broke down, then asks the student a targeted question to help them reconstruct the logic themselves. It never spoils. It models the thinking, not the conclusion. This distinction - guiding vs. telling - is the most educationally meaningful design choice in the entire product. 4. The Parent Portal Parents receive a real-time diagnostic dashboard powered by GPT-5.6. It surfaces: Live streak status and session cadence AI-synthesized cognitive grit scores (persistence under wrong answers) Subject-area vulnerability maps - which SAT domains need more time Recommended conversation starters for parent-student discussions 5. Predictive Score Modeling We model score trajectory using a saturation growth curve: predicted score rises from a student's diagnostic baseline toward 1600 at a rate driven by streak length and Border Collie Tutor accuracy. The longer Jake practices consistently and the more he reasons through corrections, the closer his projected score climbs to a perfect 1600 - the mathematical embodiment of our core promise.

### How we built it

We built Daily1600 as a modern single-page web application from the ground up during the hackathon: Front-End A single-page application built with React + TypeScript, bundled with Vite, and styled with Tailwind CSS v4. We deliberately avoided "startup neon" and instead developed a scholarly, prestigious visual identity around: Crimson (#A51C30) - ambition, urgency, excellence Gold (#C9A84C) - achievement, prestige, reward Warm parchment backgrounds - evoking academic tradition Animations include a live daily-goal progress ring, confetti bursts on milestone completions, and a mascot that reacts to student performance states. Local-First State All student progress - streaks, hearts, gems, XP, quests, badges, and active sessions - is persisted in browser localStorage via a single UserProfile JSON record. No backend is required for the full student experience. OpenAI Integration The GPT-5.6 API powers two distinct subsystems: Border Collie Tutor - invoked on every incorrect answer when VITE_ENABLE_LIVE_TUTOR=true and a server-side OPENAI_API_KEY are present. The prompt architecture includes a hard Socratic persona lock, a structured error taxonomy, and few-shot examples that distinguish guiding responses from spoiler responses. If the flag is off or the key is missing, the app falls back instantly to a deterministic authored response - guaranteeing 100% uptime with zero console errors. Parent Diagnostics - GPT-5.6 synthesizes 7 days of practice telemetry into clear mastery metrics and non-nagging conversation starters, translating raw performance numbers into actionable parent-facing narratives. AI-Assisted Development with Codex We used Codex throughout the build to accelerate the local state model, gamification hooks, UI components, Tailwind class auditing, and TypeScript type checking - dramatically compressing what would typically be a weeks-long build into hackathon timelines.

### Challenges we ran into

Taming the Border Collie Tutor Prompt GPT-5.6's default instinct is helpfulness - meaning it wants to solve problems for the student. Our earliest prototypes would gesture at the Socratic method and then casually reveal the answer in the third sentence. Getting the system prompt right required more than 20 iterations. The breakthrough was a three-layer architecture: (1) a hard persona lock ("You are a Socratic tutor. You NEVER state the correct answer or confirm/deny answer choices."), (2) a structured error taxonomy the model must classify before responding, and (3) explicit few-shot examples of what a Socratic response looks like versus a "spoiler" response. Once all three layers were in place, tutor behavior became reliably pedagogical. Keyless Uptime Architecture We needed the app to work flawlessly in a demo environment with or without a live API key. Building the dual-engine fallback - where the authored local response is shown immediately and the live GPT-5.6 stream silently overlays it if available - required careful state sequencing to avoid flickering, duplicate responses, or raw network errors surfacing to the user. Prestigious Visual Identity Under Pressure Pivoting mid-build from an initial playful neon design to a scholarly crimson-and-gold palette required a systematic audit of every Tailwind utility class and hardcoded hex in the codebase. Color consistency at this level of branding detail matters more than expected to the final premium feel of the product.

### Accomplishments we're proud of

The Border Collie Tutor genuinely works. Watching Jake reason his way to a correct answer he initially missed - without being given it - is the most satisfying thing we built. It validates the entire pedagogical premise. A real student co-built and uses it daily. Jake isn't a test user - he's the co-designer, primary tester, and daily user preparing for his actual Digital SATs. Every UX decision was validated against real 15-year-old behavior. The dual-engine fallback is seamless. The app delivers 100% uptime whether a live API key is present or not, with no console errors and no user-visible degradation. We shipped a complete, polished product with a full gamification economy, working Parent Portal, and live GPT-5.6 integration - in hackathon time. The animations and transitions feel premium, not rushed.

### What we learned

Active struggle beats passive consumption. Every time we were tempted to make the Border Collie Tutor more "helpful" by giving more direct hints, we reminded ourselves of the research: retrieval practice and productive struggle produce dramatically better long-term retention than re-reading or watching worked examples. The Socratic constraint isn't a feature limitation - it's the most important educational design decision in the product. Gamification works because it externalizes internal motivation scaffolds. Streaks, hearts, and gem economies don't create motivation - they give pre-existing motivation somewhere concrete to attach. The student who already wants to do better has a daily anchor. The student who's drifting gets a natural re-entry point every morning. Prompt engineering is real engineering. The Border Collie Tutor prompt architecture is as much a design artifact as the React components. It has structure, invariants, edge cases, and failure modes. Treating it with the same rigor as code - versioning, testing, iterating - was the unlock. Codex compresses feedback loops. Having AI assistance on state modeling, component scaffolding, and TypeScript verification meant we could maintain design quality and move fast. The bottleneck shifted from "can we build this?" to "what's the right thing to build?" - which is where it should be.

### What's next

for the Daily1600 app Peer-to-Peer Streak Duels - Study clubs where students wager gems against friends to maintain the longest active streak, adding a social accountability layer Adaptive Question Selection - Using GPT to dynamically select the next question based on the student's live error pattern, moving from fixed tracks to truly personalized pacing Offline Syllabus Mode - Downloadable practice packs for students without consistent internet (school buses, commutes) Structured PDF Diagnostic Reports - Formal printable reports parents can share with school guidance counselors or college advisors, generated by GPT from cumulative session data School Partnership Tier - A classroom dashboard that lets teachers monitor cohort-level SAT readiness across their students, with GPT-synthesized class-wide vulnerability reports

## README (from the GitHub repository)

# Daily1600 — Adaptive Digital SAT Prep

> OpenAI Build Week 2026 Submission — Education Track

![Built with Codex](https://img.shields.io/badge/Built%20with-Codex-111111?style=flat-square)
![React + Vite + Tailwind CSS](https://img.shields.io/badge/React%20%2B%20Vite%20%2B%20Tailwind%20CSS-61DAFB?style=flat-square)
![Offline-First](https://img.shields.io/badge/Offline--First-4B4B4B?style=flat-square)

**Publication status:** live and public. GitHub repository: [github.com/klee718/Daily1600](https://github.com/klee718/Daily1600) · Live demo: [daily1600.vercel.app](https://daily1600.vercel.app)

## The problem

SAT preparation can be hard to sustain: students need short, useful practice that reacts to what they miss, while parents need a clear view of progress without turning every study session into a stressful check-in.

## The solution

Daily1600 turns Digital SAT practice into a focused daily loop. A student chooses Math or English, completes targeted multiple-choice questions, receives hints and explanations, and keeps progress through XP, streaks, hearts, gems, quests, badges, a skill roadmap, and a parent-facing progress report.

The experience was co-designed and tested with a 15-year-old student so that the pace, feedback, and controls stay practical for a real Digital SAT test-taker.

## What to try

- Choose **Math** or **English** from the student dashboard and start a subject-specific session.
- Use a hint before answering, then review the explanation after each answer.
- Open the **Border Collie Tutor** after an answer for a guided, multi-turn prompt that helps the student reason through the question.
- Refresh the browser: progress, active sessions, gems, hearts, streaks, quests, and profile settings persist locally.
- Open **Parent View** for XP history, category performance, and deterministic, data-based next-step recommendations.

## Active architecture

Daily1600 is a browser-first React single-page app. The active product path is deterministic and does not depend on a server, a model API, or an API key.

| Layer | Active implementation |
| --- | --- |
| UI | React, TypeScript, Vite, Tailwind CSS v4, Motion, Recharts, and an emoji-based icon system |
| Questions | Authored static SAT-style question bank with subject/category filtering |
| Student state | One `UserProfile` JSON record in browser `localStorage` |
| Practice loop | Persisted active sessions, answer feedback, hints, XP, performance telemetry, and subject-specific question selection |
| Habit loop | Streaks, freezes, hearts, gems, wagers, quests, badges, roadmap levels, and checkpoints |
| Parent reporting | Local telemetry calculations, rolling XP trend data, and deterministic learning narratives |

The interface uses an academic crimson-and-gold palette on a light parchment surface. It is not affiliated with or endorsed by any university.

## Border Collie Tutor

The Border Collie Tutor is an authored, deterministic Socratic coaching engine in `src/components/PracticeSession.tsx`. It uses the question, the student's selected answer, and a short reply to give a guiding clue and follow-up question before revealing a full explanation. This is an intentional product decision: the tutor is instant, reliable offline, has no API cost, and avoids spoiling the answer in its first response.

## AI Architecture & Dual-Engine Fallback Strategy

Daily1600 defaults to a deterministic local study experience. If a deployment enables `VITE_ENABLE_LIVE_TUTOR=true` and supplies a server-side `OPENAI_API_KEY`, the Border Collie Tutor streams a GPT-5.6 response from `/api/tutor` with the question, selected answer, and conversation history. The browser never receives the API key.

If the flag is off, the key is missing, or the live request fails, the tutor immediately keeps the authored local Socratic response already shown in the panel. This keyless uptime path avoids raw network errors and keeps the practice session usable on static or public hosting. It is designed to meet the hackathon's viability and technical-implementation expectations without making the demo depend on a network service.

## Optional hosted API routes

The `api/` directory provides optional hosted integrations. The local app does not call them unless the public live-tutor feature switch is explicitly enabled.

| Route | Designed responsibility | Current status |
| --- | --- | --- |
| `api/generate-questions.ts` | Creates bounded batches of SAT questions and validates the required question shape before returning them | Dormant |
| `api/tutor.ts` | Streams tutor output with Server-Sent Events and carries prior conversation context | Optional live engine |
| `api/insight.ts` | Returns structured student/parent learning insights from supplied telemetry | Optional hosted route |

These routes expect a server environment and an `OPENAI_API_KEY`. `.env.example` documents the server-only key and a non-secret public feature switch; no key is needed for normal local use.

`vercel.json` is included for a future Vercel deployment: it serves the SPA entry point for browser routes while keeping `/api/*` available for the dormant server-route designs.

## How Codex contributed

Codex accelerated the implementation of the local profile/session state machine, gamification mechanics, Tailwind v4 component styling, and the parent-reporting experience. Two design-review passes also led to concrete fixes:

- Removed a root `font-size` override that inflated every rem-based layout value.
- Replaced a raw Markdown report rendered in a `<pre>` block with the structured `DetailedProgressReport` component.

The result is an offline-first study experience that remains functional during a demo even without network access.

## Run locally

```bash
git clone https://github.com/klee718/Daily1600
cd Daily1600
npm install
npm run dev
```

Open the localhost URL printed by Vite. No environment variables or API key are required.

To create a production build locally:

```bash
npm run build
npm run preview
```

On Windows PowerShell, use `npm.cmd` if PowerShell's execution policy prevents `npm` from running:

```powershell
npm.cmd run dev
```

## Project map

```text
src/
  App.tsx                         Application state and view switching
  components/PracticeSession.tsx  Practice flow and Border Collie Tutor
  components/StudentDashboard.tsx Student dashboard
  components/ParentPortal.tsx     Lazy-loaded parent view
  components/DetailedProgressReport.tsx
                                  Structured parent progress report
  data/questions.ts               Static SAT-style question bank
  lib/storage.ts                  localStorage profile persistence
  lib/learningNarrative.ts        Deterministic parent narratives
  lib/xpTrend.ts                  Rolling seven-day XP data
api/                              Dormant future server-route designs
```

## Submission details
- **Youtube Video:** [youtube](https://youtu.be/MBnbT7vDm6I)
- **GitHub repository:** [github.com/klee718/Daily1600](https://github.com/klee718/Daily1600)
- **Live demo:** [daily1600.vercel.app](https://daily1600.vercel.app)
- **Codex Session ID:** `019f62bf-fd51-7873-9786-12462f4e32e6`


## Detected evidence (automated analysis)

Indexed codebase: 20 recognized source files, 121 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Express (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- Tailwind CSS (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (29 of 29)

```
.env.example
.gitignore
api/_shared.ts
api/generate-questions.ts
api/insight.ts
api/tutor.ts
index.html
LICENSE
package.json
README.md
src/App.tsx
src/components/DetailedProgressReport.tsx
src/components/ParentPortal.tsx
src/components/PracticeSession.tsx
src/components/StudentDashboard.tsx
src/data/questions.generated.json
src/data/questions.ts
src/index.css
src/lib/ai.ts
src/lib/learningNarrative.ts
src/lib/storage.ts
src/lib/xpTrend.ts
src/main.tsx
src/vite-env.d.ts
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vercel.json
vite.config.ts
```

### Dependencies

- package.json: @tailwindcss/vite@^4.1.0, @types/react@^19.0.0, @types/react-dom@^19.0.0, @vitejs/plugin-react@latest, motion@latest, react@^19.0.0, react-dom@^19.0.0, recharts@latest, typescript@~5.7.2, vite@latest

### Recent commits (newest first)

- Update README.md file
- Update README.md file
- feat: initial release — Daily1600 adaptive Digital SAT prep app
- Initial commit

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

### package.json

```
{
  "name": "daily1600",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@tailwindcss/vite": "^4.1.0",
    "@vitejs/plugin-react": "latest",
    "motion": "latest",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "recharts": "latest"
  },
  "devDependencies": {
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "typescript": "~5.7.2",
    "vite": "latest"
  }
}

```

### src/main.tsx

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

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

```

### src/App.tsx

```typescript
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
import { questionBank, type SATQuestion, withOptionExplanations } from './data/questions'
import generatedQuestionBank from './data/questions.generated.json'
import { computeXpTrend } from './lib/xpTrend'
import { Dashboard } from './components/StudentDashboard'
import { Practice } from './components/PracticeSession'
import { getProfile, saveProfile, type ActiveSession, type Track, type UserProfile } from './lib/storage'

const HEARTS_MAX = 30
const HEART_REGEN_INTERVAL_MS = 4 * 60 * 60 * 1000
const HEART_REFILL_GEM_COST = 50
const FREEZE_COST = 75
const STREAK_REPAIR_GEM_COST = 200
const STREAK_REPAIR_WINDOW_MS = 48 * 60 * 60 * 1000
const MODULE_MAX_LEVEL = 5
const QUESTION_BANK_VERSION = 3
const ParentPortal = lazy(() => import('./components/ParentPortal'))

const roadmap: Record<Track, { id: string; name: string; categories: string[]; unit: 1 | 2 }[]> = {
  math: [
    { id: 'math-algebra', name: 'Algebra', categories: ['Algebra'], unit: 1 },
    { id: 'math-advanced', name: 'Advanced Math', categories: ['Advanced Math'], unit: 1 },
    { id: 'math-geometry', name: 'Geometry', categories: ['Geometry'], unit: 2 },
  ],
  english: [
    { id: 'english-conventions', name: 'Standard English', categories: ['Standard English Conventions'], unit: 1 },
    { id: 'english-craft', name: 'Craft & Structure', categories: ['Craft and Structure'], unit: 1 },
    { id: 'english-information', name: 'Information & Ideas', categories: ['Information and Ideas'], unit: 2 },
  ],
}

const questPool = [
  { id: 'questions-5', label: 'Answer 5 questions', kind: 'questions', target: 5, reward: 20 },
  { id: 'questions-10', label: 'Answer 10 questions', kind: 'questions', target: 10, reward: 35 },
  { id: 'xp-30', label: 'Earn 30 XP', kind: 'xp', target: 30, reward: 25 },
  { id: 'xp-60', label: 'Earn 60 XP', kind: 'xp', target: 60, reward: 45 },
  { id: 'perfect', label: 'Finish a perfect session', kind: 'perfect', target: 1, reward: 50 },
] as const

function dateInZone(timezone: string, value = new Date()): string {
  return new Intl.DateTimeFormat('en-CA', { timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit' }).format(value)
}
function daysBetween(a: string, b: string): number { return Math.round((Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`)) / 86400000) }
function seededQuests(date: string) {
  let seed = [...date].reduce((n, char) => n + char.charCodeAt(0), 0)
  return [...questPool].sort(() => ((seed = (seed * 9301 + 49297) % 233280) / 233280) - 0.5).slice(0, 3)
}
export function computeRegeneratedHearts(hearts: number, lastHeartLostAt?: string): { hearts: number; lastHeartLostAt?: string } {
  if (hearts >= HEARTS_MAX || !lastHeartLostAt) return { hearts, lastHeartLostAt }
  const elapsed = Date.now() - Date.parse(lastHeartLostAt)
  const gained = Math.floor(elapsed / HEART_REGEN_INTERVAL_MS)
  if (!gained) return { hearts, lastHeartLostAt }
  const nextHearts = Math.min(HEARTS_MAX, hearts + gained)
  return { hearts: nextHearts, lastHeartLostAt: nextHearts === HEARTS_MAX ? undefined : new Date(Date.parse(lastHeartLostAt) + gained * HEART_REGEN_INTERVAL_MS).toISOString() }
}
function playTone(type: 'correct' | 'wrong' | 'click' | 'fanfare', muted?: boolean) {
  if (muted) return
  const Ctx = window.AudioContext || window.webkitAudioContext
  if (!Ctx) return
  const ctx = new Ctx(); const osc = ctx.createOscillator(); const gain = ctx.createGain()
  osc.frequency.value = type === 'correct' ? 660 : type === 'wrong' ? 130 : type === 'fanfare' ? 880 : 420
  gain.gain.setValueAtTime(0.06, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + (type === 'fanfare' ? .45 : .15))
  osc.connect(gain).connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + (type === 'fanfare' ? .45 : .15))
}
declare global { interface Window { webkitAudioContext?: typeof AudioContext } }

export default function App() {
  const [profile, setProfile] = useState<UserProfile>(() => getProfile())
  const [view, setView] = useState<'dashboard' | 'parent' | 'settings'>('dashboard')
  const [feedback, setFeedback] = useState<string | null>(null)
  const [summary, setSummary] = useState<{ correct: number; total: number; xp: number; perfect: boolean; hintsUsed: number } | null>(null)
  const [notice, setNotice] = useState<string | null>(null)
  const today = dateInZone(profile.timezone)
  const allQuestionPool = [...(profile.generatedQuestions || []), ...(generatedQuestionBank as SATQuestion[]), ...questionBank].map(withOptionExplanations)

  useEffect(() => { saveProfile(profile) }, [profile])
  useEffect(() => {
    setProfile((p) => {
      const healed = computeRegeneratedHearts(p.hearts, p.lastHeartLostAt)
      const today = dateInZone(p.timezone)
      let next = { ...p, ...healed }
      if (p.lastActiveDate !== today) next = { ...next, lastActiveDate: today, questionsAnsweredToday: 0, todayTrackQuestions: { math: 0, english: 0 }, dailyQuestProgress: {}, dailyQuestsClaimed: [], dailyQuestDate: today }
      if (p.lastCompletedDate && daysBetween(p.lastCompletedDate, today) === 2 && p.streakFreezeCount > 0) next = { ...next, streakFreezeCount: p.streakFreezeCount - 1, lastCompletedDate: dateInZone(p.timezone, new Date(Date.now() - 86400000)) }
      if (next.currentSession && next.currentSession.questionBankVersion !== QUESTION_BANK_VERSION) {
        next = { ...next, currentSession: null }
      }
      return next
    })
  }, [])

  const quests = useMemo(() => seededQuests(today), [today])
  const active = profile.currentSession && !profile.currentSession.completed ? profile.currentSession : null
  const unansweredQuestion = active ? allQuestionPool.find((q) => q.id === active.questionIds.find((id) => !active.answers[id])) : undefined
  const currentQuestion = active && feedback
    ? allQuestionPool.find((q) => q.id === feedback)
    : unansweredQuestion
  const answered = active ? Obje
[truncated — 13354 more characters]
```

### vite.config.ts

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

export default defineConfig({ plugins: [react(), tailwindcss()] })

```

### index.html

```html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="description" content="Daily1600 is a local-first Digital SAT practice app for building a daily study habit." /><link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='14' fill='%23A51C30'/%3E%3Ctext x='32' y='43' text-anchor='middle' font-family='Arial' font-size='36' font-weight='700' fill='white'%3ED%3C/text%3E%3C/svg%3E" /><title>Daily1600</title></head><body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body></html>

```

### src/vite-env.d.ts

```typescript
/// <reference types="vite/client" />

```

### api/_shared.ts

```typescript
export const OPENAI_MODEL = 'gpt-5.6'

export function json(status: number, body: unknown) {
  return { statusCode: status, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }
}

export async function openAIJson(instructions: string, input: string) {
  const key = process.env.OPENAI_API_KEY
  if (!key) throw new Error('OPENAI_API_KEY is not configured')
  const response = await fetch('https://api.openai.com/v1/responses', {
    method: 'POST',
    headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: OPENAI_MODEL, instructions, input, text: { format: { type: 'json_object' } } }),
  })
  if (!response.ok) throw new Error(`OpenAI request failed: ${response.status}`)
  const data = await response.json() as { output_text?: string }
  if (!data.output_text) throw new Error('Model returned no text')
  return JSON.parse(data.output_text)
}

```

### api/insight.ts

```typescript
import { json, openAIJson } from './_shared'

const PARENT_DIAGNOSTIC_SYSTEM_PROMPT = `You are the Daily1600 parent-progress analyst. Use only the supplied telemetry; never invent scores, streaks, or practice history. Write concise, encouraging, parent-friendly JSON with exactly studentMessage, parentSummary, and recommendedFocus. Explain one measured strength, one growth opportunity, and one low-friction next step. Describe weak performance as an opportunity, avoid test-prep jargon, and do not diagnose a student.`

function validInsight(result: unknown): result is { studentMessage: string; parentSummary: string; recommendedFocus: string } {
  return Boolean(result) && typeof (result as any).studentMessage === 'string' && typeof (result as any).parentSummary === 'string' && typeof (result as any).recommendedFocus === 'string'
}

export default async function handler(request: any, response: any) {
  if (request.method !== 'POST') return response ? response.status(405).end() : json(405, { error: 'Method not allowed' })
  try {
    const result = await openAIJson(PARENT_DIAGNOSTIC_SYSTEM_PROMPT, `Analyze this local student telemetry. Return only JSON in this shape: {"studentMessage":"string","parentSummary":"string","recommendedFocus":"string"}. Keep each prose field under 45 words. Telemetry: ${JSON.stringify(request.body || {})}`)
    if (!validInsight(result)) throw new Error('Invalid insight')
    return response ? response.status(200).json(result) : json(200, result)
  } catch { return response ? response.status(503).json({ error: 'Insight unavailable' }) : json(503, { error: 'Insight unavailable' }) }
}

```

### api/generate-questions.ts

```typescript
import { json, openAIJson } from './_shared'

const QUESTION_AUTHOR_SYSTEM_PROMPT = `You are the Daily1600 Digital SAT item author. Produce original, accurate, age-appropriate multiple-choice questions only. Every item must test the requested subject/category, have exactly four plausible choices, one unambiguous correct index, a brief non-spoiling hint, and an explanation grounded in the prompt. Do not reuse excluded topics or IDs, do not cite unavailable passages, and output strict JSON only.`

function valid(question: any) {
  return typeof question?.questionText === 'string' && Array.isArray(question.options) && question.options.length === 4 && Number.isInteger(question.correctIndex) && question.correctIndex >= 0 && question.correctIndex < 4 && typeof question.explanation === 'string' && typeof question.hint === 'string'
}

export default async function handler(request: any, response: any) {
  if (request.method !== 'POST') return response ? response.status(405).end() : json(405, { error: 'Method not allowed' })
  const { subject, category, difficulty, count = 5, excludeIds = [] } = request.body || {}
  const prompt = `Create ${Math.min(count, 10)} original Digital SAT ${subject} multiple-choice questions for category ${category || 'mixed'}, difficulty ${difficulty || 3}. Never repeat these excluded ids/topics: ${JSON.stringify(excludeIds.slice(-30))}. Return only JSON: {"questions":[{"id":"unique-id","subject":"${subject}","category":"string","questionText":"string","passageText":"optional string","options":["a","b","c","d"],"correctIndex":0,"hint":"one brief guiding step that does not reveal the answer","explanation":"brief accurate explanation","difficulty":1}]}. Questions must be accurate, age-appropriate, and have exactly four choices.`
  try {
    for (let attempt = 0; attempt < 2; attempt++) {
      const data = await openAIJson(QUESTION_AUTHOR_SYSTEM_PROMPT, prompt)
      const questions = Array.isArray(data.questions) ? data.questions.filter(valid) : []
      if (questions.length) return response ? response.status(200).json({ questions }) : json(200, { questions })
    }
    throw new Error('Generated questions did not validate')
  } catch (error) {
    return response ? response.status(503).json({ error: 'Question generation unavailable' }) : json(503, { error: 'Question generation unavailable' })
  }
}

```

### api/tutor.ts

```typescript
import { OPENAI_MODEL } from './_shared'

const SOCRATIC_TUTOR_SYSTEM_PROMPT = `You are the Daily1600 Border Collie Tutor: warm, precise, and encouraging. Ground every statement in the supplied question, passage, answer choices, and performance context. When a student first asks after an incorrect answer, do not reveal the correct option or give a full answer-key breakdown. Instead, identify the student's chosen option by letter, give one targeted clue about the SAT rule or formula, and end with one short question that asks the student to reconsider the evidence. In later turns, if the student has attempted the reasoning or explicitly asks for the answer, give a concise explanation of why the correct option works and why each incorrect option fails. Never invent rules, scores, or source material.`

export default async function handler(request: any, response: any) {
  if (request.method !== 'POST') return response.status(405).end()
  if (!process.env.OPENAI_API_KEY) {
    console.warn('OpenAI API key missing. Utilizing local Socratic fallback engine.')
    return response.status(503).json({ error: 'Tutor unavailable' })
  }
  const { question, studentAnswerIndex, correctIndex, priorMessages = [], performanceSummary = '' } = request.body || {}
  const input = `Question: ${JSON.stringify(question)}\nStudent chose option index ${studentAnswerIndex}; correct index ${correctIndex}.\nPerformance context: ${performanceSummary}\nPrior conversation: ${JSON.stringify(priorMessages)}\nRespond to the student now.`
  try {
    const upstream = await fetch('https://api.openai.com/v1/responses', { method: 'POST', headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: OPENAI_MODEL, stream: true, instructions: SOCRATIC_TUTOR_SYSTEM_PROMPT, input }) })
    if (!upstream.ok || !upstream.body) throw new Error('Upstream unavailable')
    response.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' })
    const reader = upstream.body.getReader(); const decoder = new TextDecoder(); let buffer = ''
    while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) if (line.startsWith('data: ')) { try { const event = JSON.parse(line.slice(6)); if (event.type === 'response.output_text.delta') response.write(`data: ${JSON.stringify({ delta: event.delta })}\n\n`) } catch {} } }
    response.write('data: [DONE]\n\n'); response.end()
  } catch (error) {
    console.warn('OpenAI API request failed. Utilizing local Socratic fallback engine.', error)
    response.status(503).json({ error: 'Tutor unavailable' })
  }
}

```

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