# Project export: CineMatch AI

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: An AI-powered entertainment recommendation engine that learns your preferences through live interactions and explains every recommendation using GPT-5.6.
- Devpost: https://devpost.com/software/learnos-multi-agent-ai-learning-platform
- GitHub: https://github.com/UltraV1be/CineMatch-AI
- Video: https://www.youtube.com/embed/w1Abo2SHgso?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — UltraV1be (1 commits)

## Devpost submission (written by the team)

### Overview

Find your perfect movie or TV series without endless scrolling. We've all experienced the same problem. We open Netflix, Prime Video, Disney+, or another streaming platform hoping to relax, but instead spend 20–30 minutes scrolling through hundreds of titles without deciding what to watch. Most recommendation systems rely heavily on watch history, trending content, or basic genre filtering. While useful, they often fail to understand what a user actually wants to watch at that specific moment. Sometimes you're looking for an emotional story. Sometimes you want a mind-bending thriller. Sometimes you just need a light comedy after a long day. That simple problem inspired CineMatch AI. Instead of making users search through thousands of movies and TV series, CineMatch AI tries to understand their preferences through an interactive conversation and continuously improves recommendations as more information becomes available. CineMatch AI is an intelligent entertainment discovery platform that recommends: 🎬 Movies 📺 TV Series 🎭 TV Dramas 🍿 Anime 📚 Documentaries 🌍 International Content Rather than presenting users with endless filters, CineMatch AI gradually builds a preference profile by asking adaptive questions. Every answer helps refine the recommendation engine, making suggestions more accurate over time. Traditional recommendation systems usually work like this: CineMatch AI follows a different philosophy. Instead of removing movies through strict filters, every answer adjusts the ranking of all candidates. The more the user interacts, the better the recommendations become. Adaptive questionnaire instead of long static forms Real-time recommendation updates Progressive preference learning Explainable recommendations Rich movie information Responsive UI Modern user experience Dynamic ranking engine The user starts with a few basic questions. Every answer updates a continuously evolving preference profile. The recommendation engine recalculates match scores instantly. Movies and TV shows are re-ranked in real time. Users can open any recommendation to view detailed information before deciding what to watch. No unnecessary searching. No endless scrolling. Just personalized recommendations. CineMatch AI is designed around one core principle: Recommendations should become better—not disappear—as users answer more questions. Instead of filtering everything out, every answer contributes to a weighted recommendation score. The system evaluates factors such as: Genres Themes Mood Runtime Language Story complexity IMDb preference Emotional tone Watching context Content to avoid These signals work together to continuously improve ranking quality. Recommendations shouldn't feel random. Every recommendation includes a short explanation describing why it matches the user's preferences. Example: "This movie ranks highly because it combines psychological suspense, intelligent storytelling, and emotional depth while matching your preferred language and runtime." This makes the recommendation process transparent and easier to trust. Frontend Next.js React TypeScript Tailwind CSS Framer Motion shadcn/ui Backend Node.js REST APIs Recommendation Engine Progressive user profiling Weighted scoring Semantic matching Explainable recommendations Smart caching Building CineMatch AI involved several challenges. The biggest challenge was designing a recommendation engine that updates recommendations instantly while keeping the interface responsive. Another challenge was ensuring that recommendations become increasingly accurate without becoming too restrictive. Balancing responsiveness, scalability, and recommendation quality required multiple iterations of the recommendation pipeline. Developing CineMatch AI taught me that recommendation systems are much more than simple filters. Throughout this project I learned: Recommendation system design Progressive user profiling Scalable frontend architecture Explainable recommendation logic State management Performance optimization Modular software architecture This project is only the beginning. Planned improvements include: Semantic analysis of audience reviews AI-powered adaptive conversations Better understanding of story themes Group recommendations Personalized watchlists Streaming platform availability Voice interaction Natural language search Regional recommendations Improved multilingual support The long-term vision is to transform CineMatch AI into an intelligent entertainment companion that understands every user's unique preferences. CineMatch AI was built around one simple idea: People should spend less time searching and more time enjoying great stories. This project combines adaptive questioning, progressive recommendation ranking, explainable results, and a modern user experience to create a smarter way of discovering movies and TV shows. While this version is an MVP, it lays the foundation for a much more capable AI-powered entertainment discovery platform in the future.

## README (from the GitHub repository)

# 🎬 CineMatch AI — OpenAI Build Week Challenge Submission

> **Next-Gen Interactive Movie & Show Discovery Engine powered by GPT-5.6 & Codex**

[![OpenAI Build Week](https://img.shields.io/badge/OpenAI%20Build%20Week-2026-blueviolet.svg)](https://devpost.com)
[![Track](https://img.shields.io/badge/Track-Apps%20for%20Your%20Life-emerald.svg)](#-challenge-track)
[![Next.js](https://img.shields.io/badge/Next.js-15.5-black.svg)](https://nextjs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/)

---

## 📌 Submission Overview

| Submission Field | Details |
| :--- | :--- |
| **Project Name** | CineMatch AI |
| **Challenge** | OpenAI Build Week Challenge (July 2026) |
| **Track** | **Apps for your life** |
| **Submission Category** | Consumer Apps for Everyday Life — Entertainment & Decision-Fatigue Elimination |
| **Code Repository** | Private/Public GitHub Repository (Shared with `testing@devpost.com` & `build-week-event@openai.com`) |
| **Demo Video** | [Watch Demo Video on YouTube](https://youtube.com) *(< 3 min demo video covering project workflow & Codex/GPT-5.6 usage)* |
| **Codex Session ID** | `/feedback` Session ID: `codex-cinematch-ai-build-week-2026` |

---

## 🚀 What is CineMatch AI?

**CineMatch AI** is an intelligent, zero-scrolling movie and TV series recommendation platform engineered to end decision fatigue. Instead of endless list browsing or rigid genre filters, CineMatch AI engages users with adaptive, multi-dimensional queries (mood, thematic pacing, visual style, runtime preferences) and synthesizes personalized watch recommendations powered by semantic matching, dynamic confidence scoring, and AI explanation generation.

### 🌟 Key Features
- **Adaptive Preference Engine**: Asks intelligent, contextual questions that adapt dynamically based on your previous answers.
- **Hybrid Semantic & Scoring Algorithm**: Combines direct keyword extraction, genre weightings, and real-time metadata calculation for high-confidence match scores.
- **OMDb API Integration & Live Enrichment**: Augments local catalog items with real-time posters, IMDb ratings, plot summaries, and awards metadata.
- **AI Explanation Generation**: Explains *why* a particular title was picked specifically for you based on your unique choices.
- **Sleek, Premium Dark-Theme UI**: Built with Next.js 15 App Router, Tailwind CSS, Lucide Icons, and Framer Motion micro-animations.
- **Zero Cold-Start / Offline Fallback**: Features a rich built-in movie dataset so judges and users can test instantly without mandatory API keys.

---

## 🎯 Challenge Track

### Track: **Apps for your life**
CineMatch AI directly targets a universal everyday problem: **streaming decision paralysis**. By transforming title discovery into an engaging, interactive conversation and instant visual recommendation deck, it delivers a delightful everyday consumer app experience.

---

## 🛠️ How GPT-5.6 and Codex Were Used

Developing CineMatch AI end-to-end within the hackathon timeframe was made possible through deep collaboration with **Codex** powered by **GPT-5.6**.

### ⚡ Where Codex Accelerated Development
1. **Architectural Scaffolding**: Rapidly generated the full Next.js 15 App Router directory layout, custom hooks, and modular service handlers (`lib/recommendation-engine.ts`, `lib/scoring.ts`, `lib/omdb.ts`).
2. **Algorithm Engineering**: Formulated and refined the multi-layered score calculator (`lib/score-calculator.ts`) combining base genre weights, semantic tag overlap, and confidence metrics.
3. **API Integration & Resilience**: Implemented seamless fallback mechanisms between OMDb API queries and offline JSON data (`data/movies.ts` & `data/movies.json`).
4. **UI Component Design**: Crafted modern glassmorphism UI components with dynamic page transitions and responsive layouts (`components/hero.tsx`, `components/recommendation-card.tsx`).

### 💡 Key Decisions Made with Codex & GPT-5.6
- **Adaptive vs. Fixed Questionnaires**: Codex suggested switching from static multi-choice forms to an adaptive question graph (`data/adaptive-questions.ts`), improving recommendation accuracy while keeping user interaction brief.
- **Transparent Confidence Scores**: Integrated a transparent confidence meter (`lib/recommendation-confidence.ts`) to build user trust in AI-curated choices.
- **Decoupled Data Providers**: Designed modular content provider abstractions (`lib/content-provider.ts`) so metadata sources can be swapped or augmented seamlessly.

---

## 🏗️ Project Architecture & Data Flow

```
[ User Prompt / Adaptive Quiz ]
               │
               ▼
   [ Preference Normalizer ] ──► (lib/normalizer.ts)
               │
               ▼
   [ Semantic Matcher & Scoring ] ──► (lib/semantic-matcher.ts & score-calculator.ts)
               │
               ├──────────────────────────┐
               ▼                          ▼
   [ Local Data Fallback ]      [ OMDb API Enrichment ]
     (data/movies.ts)             (lib/omdb.ts)
               │                          │
               └────────────┬─────────────┘
                            ▼
           [ AI Explanation & Confidence ] ──► (lib/explanation-generator.ts)
                            │
                            ▼
              [ Next.js Interactive UI ] ──► (app/recommendations/page.tsx)
```

---

## 💻 Quick Start & Setup Instructions

Follow these step-by-step instructions to set up and run **CineMatch AI** locally.

### Prerequisites
- **Node.js**: v18.17.0 or higher
- **npm** or **pnpm** / **yarn**

### 1. Clone & Navigate to Repository
```bash
git clone https://github.com/your-username/cinematch-ai.git
cd "CineMatch AI"
```

### 2. Install Dependencies
```bash
npm install
```

### 3. Environment Variables (Optional)
CineMatch AI works out-of-the-box using built-in sample data. To enable live OMDb movie poster and rating enrichment, create or update `.env.local` in the root directory:

```env
OMDB_API_KEY=your_omdb_api_key_here
OPENAI_API_KEY=your_openai_api_key_here
```

### 4. Run Development Server
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) in your browser to test the application.

### 5. Typecheck & Build Validation
```bash
# Verify TypeScript types
npm run typecheck

# Production Build
npm run build
```

---

## 📊 Sample Data & Testing Guidance for Judges

To facilitate effortless testing by Devpost and OpenAI judges:

1. **Immediate Playability**: You do **not** need an external API key to run and evaluate CineMatch AI. The project includes high-quality sample datasets located in:
   - `data/movies.ts` & `data/movies.json`: Curated dataset with genres, themes, cast, and plot points.
   - `data/questions.json` & `data/adaptive-questions.ts`: Pre-configured adaptive questionnaire tree.
2. **Interactive Flow**:
   - Navigate to the homepage at `/`.
   - Click **"Find My Match"** to start the interactive quiz flow.
   - Answer preference prompts regarding mood, pacing, and genre.
   - Explore the personalized recommendation deck complete with confidence scores, stream matches, and personalized AI explanations.

---

## 🤝 Repository Access for Judges

This repository has been configured and shared for hackathon evaluation:
- Shared with Devpost Testing Team: `testing@devpost.com`
- Shared with OpenAI Event Team: `build-week-event@openai.com`

---

## 📜 License

Distributed under the MIT License. See `LICENSE` for more information.

---

<p center="text-center">Built with ❤️ for the <strong>OpenAI Build Week Challenge 2026</strong> using <strong>GPT-5.6 & Codex</strong>.</p>


## Detected evidence (automated analysis)

Indexed codebase: 66 recognized source files, 146 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (74 of 74)

```
.eslintrc.json
.gitignore
app/api/enrich/route.ts
app/api/question/route.ts
app/api/search/route.ts
app/globals.css
app/layout.tsx
app/match/page.tsx
app/page.tsx
app/recommendations/page.tsx
components/empty-state.tsx
components/genre-chip.tsx
components/hero.tsx
components/loading-skeleton.tsx
components/match-badge.tsx
components/movie-details-modal.tsx
components/navbar.tsx
components/option-card.tsx
components/page-transition.tsx
components/progress-bar.tsx
components/question-card.tsx
components/recommendation-card.tsx
components/recommendation-list.tsx
components/section-title.tsx
components/ui/button.tsx
data/adaptive-questions.ts
data/genres.json
data/movies.json
data/movies.ts
data/questions.json
hooks/use-adaptive-questionnaire.ts
hooks/use-explanation.ts
hooks/use-hybrid-recommendations.ts
hooks/use-movie-enrichment.ts
hooks/use-questionnaire.ts
hooks/use-recommendations.ts
hooks/use-semantic-analysis.ts
hooks/use-session-answers.ts
lib/cache-manager.ts
lib/cache.ts
lib/content-parser.ts
lib/content-provider.ts
lib/conversation-manager.ts
lib/embedding-service.ts
lib/explanation-generator.ts
lib/keyword-extractor.ts
lib/metadata-provider.ts
lib/natural-language-search.ts
lib/normalizer.ts
lib/omdb.ts
lib/poster-provider.ts
lib/poster.ts
lib/profile-builder.ts
lib/question-generator.ts
lib/ranking-engine.ts
lib/recommendation-confidence.ts
lib/recommendation-engine.ts
lib/review-provider.ts
lib/score-calculator.ts
lib/scoring.ts
lib/search-provider.ts
lib/semantic-indexer.ts
lib/semantic-matcher.ts
lib/user-profile-builder.ts
lib/utils.ts
lib/validation-engine.ts
next-env.d.ts
next.config.ts
package.json
postcss.config.mjs
README.md
tailwind.config.ts
tsconfig.json
types/index.ts
```

### Dependencies

- package.json: @types/node@^22.10.2, @types/react@^19.0.2, @types/react-dom@^19.0.2, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9.17.0, eslint-config-next@^15.5.7, framer-motion@^12.23.12, lucide-react@^0.468.0, next@^15.5.7, postcss@^8.4.49, react@^19.1.1, react-dom@^19.1.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, typescript@^5.7.2

### Recent commits (newest first)

- feat: implement initial CineMatch AI core architecture with recommendation engine, content providers, and adaptive questionnaire hooks

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

### package.json

```
{
  "name": "cinematch-ai",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.23.12",
    "lucide-react": "^0.468.0",
    "next": "^15.5.7",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "tailwind-merge": "^2.5.5"
  },
  "devDependencies": {
    "@types/node": "^22.10.2",
    "@types/react": "^19.0.2",
    "@types/react-dom": "^19.0.2",
    "autoprefixer": "^10.4.20",
    "eslint": "^9.17.0",
    "eslint-config-next": "^15.5.7",
    "postcss": "^8.4.49",
    "tailwindcss": "^3.4.17",
    "typescript": "^5.7.2"
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = { title: "CineMatch AI | Find your next obsession", description: "A thoughtful, AI-powered entertainment discovery experience." };

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return <html lang="en"><body>{children}</body></html>;
}

```

### app/page.tsx

```typescript
import { ArrowUpRight, CheckCircle2, Sparkles } from "lucide-react";
import { Hero } from "@/components/hero";
import { Navbar } from "@/components/navbar";
import { PageTransition } from "@/components/page-transition";

const highlights = ["Guided by your mood", "Recommendations update as you go", "Built for movies and series"];
export default function Home() { return <PageTransition><div className="min-h-screen overflow-hidden"><Navbar /><Hero /><section id="how-it-works" className="mx-auto grid max-w-6xl gap-5 px-5 pb-20 sm:grid-cols-3 sm:px-8"><div className="glass rounded-2xl p-6 sm:col-span-2"><p className="text-xs font-bold uppercase tracking-[.2em] text-violet-400">Designed for discovery</p><h2 className="mt-3 max-w-xl text-2xl font-bold tracking-tight sm:text-3xl">The end of endless scrolling.</h2><p className="mt-3 max-w-xl text-sm leading-6 text-zinc-400">A calm, focused flow that helps you choose something worth watching—without the decision fatigue.</p><div className="mt-6 flex flex-wrap gap-3">{highlights.map((item) => <span key={item} className="inline-flex items-center gap-2 text-xs text-zinc-300"><CheckCircle2 className="h-4 w-4 text-emerald-400" />{item}</span>)}</div></div><div className="rounded-2xl bg-gradient-to-br from-indigo-500 to-violet-700 p-6 shadow-glow"><Sparkles className="h-6 w-6" /><p className="mt-6 text-sm font-medium text-white/80">Made for your next</p><p className="text-xl font-bold">great obsession <ArrowUpRight className="inline h-5 w-5" /></p></div></section></div></PageTransition>; }

```

### types/index.ts

```typescript
export type SelectionMode = "single" | "multiple";
export type ContentType = "Movie" | "TV Series" | "Anime" | "Documentary";
export type DetailedContentType =
  | "Movie"
  | "TV Series"
  | "Anime"
  | "Documentary"
  | "TV Drama"
  | "Animation";

export type FilmIndustry =
  | "Hollywood"
  | "Bollywood"
  | "Tollywood"
  | "Kollywood"
  | "Japanese"
  | "Korean"
  | "Chinese"
  | "European"
  | "International";

export type RuntimeCategory = "Under 2 Hours" | "2-3 Hours" | "Long Runtime";

export interface QuestionOption {
  id: string;
  label: string;
  description: string;
  icon: string;
}

export interface Question {
  id: string;
  prompt: string;
  helper: string;
  mode: SelectionMode;
  options: QuestionOption[];
}

export interface AdaptiveQuestionResponse {
  question: Question | null;
  confidence: number;
  complete: boolean;
}

export interface NormalizedContentRecord {
  id: string;
  title: string;
  originalTitle?: string;
  alternativeTitles: string[];
  contentType: DetailedContentType;
  releaseYear: number;
  countryOfOrigin: string;
  filmIndustry: FilmIndustry;
  originalLanguage: string;
  verifiedDubbedLanguages: string[];
  genres: string[];
  themes: SemanticTag[];
  runtime: string;
  rating: number;
  director?: string;
  writers?: string;
  cast: string[];
  productionCompany?: string;
  plot: string;
  keywords: string[];
  trailerUrl?: string;
  posterUrl: string;
  awards?: string;
  externalIds: {
    imdbId?: string;
    tmdbId?: string;
  };
  reviewSummary?: string;
  dataSource: "Verified Catalog" | "OMDB API" | "Multi-Provider Hybrid";
  lastUpdated: string;
  confidenceScore: number; // 0 to 100%
}

export interface Movie {
  id: string;
  title: string;
  originalTitle?: string;
  alternativeTitles?: string[];
  type: ContentType;
  filmIndustry?: FilmIndustry;
  countryOfOrigin?: string;
  genres: string[];
  language: string;
  verifiedDubbedLanguages?: string[];
  rating: number;
  year: number;
  runtimeCategory: RuntimeCategory;
  runtime: string;
  platforms: string[];
  moods: string[];
  avoidTags: string[];
  posterPlaceholder: string;
  description: string;
  productionCompany?: string;
  details?: OmdbMovieDetails;
  normalized?: NormalizedContentRecord;
}

export interface OmdbMovieDetails {
  imdbId?: string;
  title: string;
  poster?: string;
  rating?: string;
  runtime?: string;
  year?: string;
  released?: string;
  language?: string;
  country?: string;
  type?: string;
  genres?: string[];
  director?: string;
  writers?: string;
  actors?: string;
  plot?: string;
  awards?: string;
}

export interface OmdbSearchResult {
  imdbId: string;
  title: string;
  year?: string;
  type?: string;
  poster?: string;
}

export type SemanticTag =
  | "dark"
  | "psychological"
  | "mind-blowing"
  | "fast-paced"
  | "slow-burn"
  | "family-friendly"
  | "violent"
  | "funny"
  | "emotional"
  | "twist-ending"
  | "character-driven"
  | "thought-provoking"
  | "inspirational"
  | "mystery"
  | "crime"
  | "suspenseful"
  | "romantic"
  | "uplifting"
  | "lighthearted"
  | "realistic"
  | "time-travel"
  | "space-exploration"
  | "artificial-intelligence"
  | "cyberpunk"
  | "post-apocalyptic"
  | "political"
  | "historical"
  | "survival"
  | "coming-of-age"
  | "philosophical"
  | "revenge"
  | "legal"
  | "true-crime";

export interface SemanticProfile {
  tags: SemanticTag[];
  source: "local" | "fallback";
}

export interface ContentProfile {
  id: string;
  title: string;
  originalTitle?: string;
  contentType: ContentType;
  filmIndustry?: FilmIndustry;
  overview: string;
  plot: string;
  genres: string[];
  keywords: string[];
  themes: SemanticTag[];
  moods: string[];
  language: string;
  verifiedDubbedLanguages?: string[];
  country: string;
  runtime: string;
  releaseYear: number;
  rating: number;
  popularity: number;
  actors: string[];
  director?: string;
  writers?: string;
  productionCompany?: string;
  awards?: string;
  platforms: string[];
  confidenceScore?: number;
}

export type SemanticEmbedding = Record<SemanticTag, number>;

export interface UserPreferenceProfile {
  type: string[];
  genres: string[];
  moods: string[];
  themes: string[];
  pacing: string[];
  language: string[];
  runtime: string;
  imdbPreference: string;
  releasePeriod: string;
  watchingWith: string;
  avoidTags: string[];
  platforms: string[];
  complexity: string[];
  emotionalIntensity: string[];
  desiredTags: SemanticTag[];
  avoidedTags: SemanticTag[];
}

export interface UserProfile extends UserPreferenceProfile {
  contentType: ContentType[];
  storyComplexity?: string;
  emotionalTone?: string;
  preferredEnding?: string;
}

export interface ScoreBreakdown {
  genreScore: number;
  themeScore: number;
  moodScore: number;
  languageScore: number;
  runtimeScore: number;
  imdbScore: number;
  semanticScore: number;
  penaltyScore: number;
  finalMatch: number;
}

export interface RecommendationScores {
  metadata: number;
  semantic: number;
  imdb: number;
  quality: number;
  genre?: number;
  theme?: number;
  mood?: number;
  penalty?: number;
  final: number;
}

export interface RankChange {
  delta: number;
  reason: string;
}

export interface ScoredMovie extends Movie {
  score: number;
  match: number;
  details?: OmdbMovieDetails;
  scores?: RecommendationScores;
  breakdown?: ScoreBreakdown;
  rankChange?: RankChange;
  explanationPreview?: string;
}

export type Answers = Record<string, string[]>;

```

### app/match/page.tsx

```typescript
"use client";

import Link from "next/link";
import { ArrowLeft, ArrowRight, ListFilter, LoaderCircle, Sparkles } from "lucide-react";
import { Brand } from "@/components/navbar";
import { ProgressBar } from "@/components/progress-bar";
import { QuestionCard } from "@/components/question-card";
import { RecommendationList } from "@/components/recommendation-list";
import { EmptyState } from "@/components/empty-state";
import { useAdaptiveQuestionnaire } from "@/hooks/use-adaptive-questionnaire";
import { useHybridRecommendations } from "@/hooks/use-hybrid-recommendations";
import { Button } from "@/components/ui/button";
import { LoadingSkeleton } from "@/components/loading-skeleton";

export default function MatchPage() {
  const flow = useAdaptiveQuestionnaire();
  const hybrid = useHybridRecommendations(flow.allAnswers);

  const selectionComplete = flow.answers.length > 0;
  const showFinal = flow.complete || flow.confidence >= 88;

  return (
    <main className="min-h-screen bg-[#09090B] text-zinc-100">
      {/* Top Header */}
      <header className="sticky top-0 z-20 border-b border-white/10 bg-[#09090B]/85 backdrop-blur-xl">
        <div className="mx-auto flex max-w-7xl items-center gap-5 px-5 py-4 sm:px-8">
          <Brand />
          <div className="ml-auto flex items-center gap-4">
            <div className="w-28 sm:w-56">
              <ProgressBar value={flow.progress} />
            </div>
            <span className="hidden text-xs font-semibold uppercase tracking-wider text-violet-300 sm:block">
              {flow.confidence}% Confidence
            </span>
          </div>
        </div>
      </header>

      {/* Main Grid */}
      <div className="mx-auto grid max-w-7xl gap-8 px-5 py-8 lg:grid-cols-[minmax(0,1fr)_420px] lg:px-8">
        {/* Questionnaire Section */}
        <section className="flex flex-col justify-between min-h-[500px]">
          <div>
            <div className="mb-5 flex items-center justify-between">
              <span className="text-xs font-bold uppercase tracking-widest text-zinc-400">
                Question {flow.index + 1} · Adaptive Tree
              </span>
              <span className="inline-flex items-center gap-1.5 rounded-full border border-violet-400/20 bg-violet-500/10 px-3 py-1 text-xs font-medium text-violet-300">
                <Sparkles className="h-3.5 w-3.5" />
                {flow.confidence}% match confidence
              </span>
            </div>

            {flow.question ? (
              <QuestionCard question={flow.question} selected={flow.answers} onToggle={flow.toggle} />
            ) : (
              <div className="glass rounded-2xl p-8 text-center">
                <h2 className="text-2xl font-bold text-white">Profile Complete!</h2>
                <p className="mt-2 text-sm text-zinc-400">
                  CineMatch has analyzed your preferences and generated custom recommendations.
                </p>
              </div>
            )}
          </div>

          <div className="mt-8 pt-4 border-t border-white/10">
            <div className="flex items-center justify-between">
              <Button variant="ghost" onClick={flow.previous} disabled={flow.isFirst || flow.generating}>
                <ArrowLeft className="h-4 w-4 mr-2" />
                Previous
              </Button>

              {showFinal ? (
                <Link href="/recommendations">
                  <Button className="bg-gradient-to-r from-violet-600 to-indigo-600 font-semibold shadow-glow hover:from-violet-500 hover:to-indigo-500">
                    See All Top Matches <ArrowRight className="h-4 w-4 ml-2" />
                  </Button>
                </Link>
              ) : (
                <Button onClick={() => void flow.next()} disabled={!selectionComplete || flow.generating}>
                  {flow.generating ? (
                    <>
                      <LoaderCircle className="h-4 w-4 animate-spin mr-2" />
                      Analyzing answers...
                    </>
                  ) : (
                    <>
                      Next Question <ArrowRight className="h-4 w-4 ml-2" />
                    </>
                  )}
                </Button>
              )}
            </div>
            <p className="mt-4 text-center text-xs text-zinc-500">
              Every selection updates candidate rankings live in the side panel.
            </p>
          </div>
        </section>

        {/* Live Recommendations Side Panel */}
        <aside
          aria-label="Live recommendations"
          className="lg:sticky lg:top-24 lg:max-h-[calc(100vh-7rem)] lg:overflow-y-auto lg:pr-1"
        >
          <div className="mb-4 flex items-center justify-between">
            <div>
              <p className="text-xs font-bold uppercase tracking-[.2em] text-violet-400">Live Ranking Feed</p>
              <h2 className="mt-1 text-lg font-bold">
                {hybrid.recommendations.length ? "Continuous Rank Shift" : "Evaluating Catalog..."}
              </h2>
            </div>
            <ListFilter className="h-5 w-5 text-zinc-500" />
          </div>

          {hybrid.recommendations.length ? (
            <>
              <div className="mb-3 h-1">{hybrid.loading && <LoadingSkeleton className="h-1 w-full" />}</div>
              <RecommendationList movies={hybrid.recommendations} compact />
            </>
          ) : hybrid.loading ? (
            <div className="space-y-3">
              {Array.from({ length: 4 }, (_, index) => (
                <LoadingSkeleton key={index} className="h-28" />
              ))}
            </div>
          ) : (
            <EmptyState title="Catalog Available" description="Select options to see live ranking movements." />
          )}
        </aside>
      </div>
    </main>
  );
}

```

### app/recommendations/page.tsx

```typescript
"use client";

import Link from "next/link";
import { AnimatePresence, motion } from "framer-motion";
import { ArrowLeft, BarChart3, Clock3, LoaderCircle, Play, Star, Tv, UserRound } from "lucide-react";
import { GenreChip } from "@/components/genre-chip";
import { MatchBadge } from "@/components/match-badge";
import { Navbar } from "@/components/navbar";
import { PageTransition } from "@/components/page-transition";
import { Poster } from "@/components/recommendation-card";
import { RecommendationList } from "@/components/recommendation-list";
import { SectionTitle } from "@/components/section-title";
import { EmptyState } from "@/components/empty-state";
import { LoadingSkeleton } from "@/components/loading-skeleton";
import { useExplanation } from "@/hooks/use-explanation";
import { useHybridRecommendations } from "@/hooks/use-hybrid-recommendations";
import { useSessionAnswers } from "@/hooks/use-session-answers";

const fallbackAnswers = { type: ["Movie"], genres: ["Sci-Fi", "Drama", "Mystery"], mood: ["Mind-blowing"] };

export default function RecommendationsPage() {
  const savedAnswers = useSessionAnswers();
  const answers = Object.keys(savedAnswers).length ? savedAnswers : fallbackAnswers;
  const hybrid = useHybridRecommendations(answers);

  const pick = hybrid.recommendations[0];
  const explanation = useExplanation(pick, answers);

  if (!pick) {
    return (
      <PageTransition>
        <div className="min-h-screen">
          <Navbar action={false} />
          <main className="mx-auto max-w-2xl px-5 py-20">
            <EmptyState title="Evaluating Catalog" description="Calculating personalized recommendations..." />
          </main>
        </div>
      </PageTransition>
    );
  }

  const genres = pick.details?.genres ?? pick.genres;
  const title = pick.details?.title ?? pick.title;

  return (
    <PageTransition>
      <div className="min-h-screen overflow-hidden text-zinc-100">
        <Navbar action={false} />
        <main className="mx-auto max-w-7xl px-5 pb-20 sm:px-8">
          <Link
            href="/match"
            className="mb-7 inline-flex items-center gap-2 text-sm font-medium text-zinc-400 transition hover:text-white"
          >
            <ArrowLeft className="h-4 w-4" /> Refine your choices
          </Link>

          {/* Top Recommendation Showcase */}
          <section className="relative overflow-hidden rounded-3xl border border-white/10 bg-gradient-to-br from-indigo-500/15 via-[#18181B] to-violet-500/10 p-6 shadow-card sm:p-9">
            <div className="absolute -right-32 -top-32 h-72 w-72 rounded-full bg-violet-500/20 blur-3xl" />
            <div className="relative grid items-center gap-8 md:grid-cols-[230px_1fr]">
              <Poster movie={pick} className="aspect-[2/3] w-full max-w-[230px] shadow-card rounded-2xl" />

              <div>
                <div className="flex flex-wrap items-center gap-3">
                  <MatchBadge match={pick.match} />
                  <span className="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs font-semibold text-zinc-300">
                    #1 Recommended Choice · {pick.details?.year ?? pick.year} · {pick.type}
                  </span>
                </div>

                <h1 className="mt-4 text-3xl font-extrabold tracking-tight text-white sm:text-5xl">{title}</h1>

                <div className="mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 text-sm text-zinc-300">
                  <span className="inline-flex items-center gap-1.5 font-semibold text-amber-300">
                    <Star className="h-4 w-4 fill-amber-400 text-amber-400" />
                    IMDb {pick.details?.rating ?? pick.rating}
                  </span>
                  <span className="inline-flex items-center gap-1.5">
                    <Clock3 className="h-4 w-4 text-zinc-400" />
                    {pick.details?.runtime ?? pick.runtime}
                  </span>
                  <span>{pick.details?.language ?? pick.language}</span>
                  {pick.platforms[0] && (
                    <span className="inline-flex items-center gap-1.5">
                      <Tv className="h-4 w-4 text-zinc-400" />
                      {pick.platforms[0]}
                    </span>
                  )}
                </div>

                <div className="mt-4 flex flex-wrap gap-2">
                  {genres.map((genre) => (
                    <GenreChip key={genre} genre={genre} />
                  ))}
                </div>

                <p className="mt-5 max-w-2xl text-sm leading-7 text-zinc-300">{pick.details?.plot ?? pick.description}</p>

                {pick.details?.director && (
                  <p className="mt-4 text-sm text-zinc-400">
                    <span className="font-semibold text-zinc-200">Director:</span> {pick.details.director}
                  </p>
                )}
                {pick.details?.actors && (
                  <p className="mt-1 inline-flex items-center gap-2 text-sm text-zinc-400">
                    <UserRound className="h-4 w-4 text-violet-300" />
                    <span>
                      <span className="font-semibold text-zinc-200">Cast:</span> {pick.details.actors}
                    </span>
                  </p>
                )}

                {/* Score breakdown stats banner */}
                {pick.breakdown && (
                  <div className="mt-5 rounded-2xl border border-white/10 bg-black/40 p-4 max-w-xl">
                    <p className="flex items-center gap-1.5 text-xs font-bold uppercase tracking-wider text-violet-300 mb-2">
                      <BarChart3 className="h-4 w-4" /> Match Score Decomposition
                    </p>
                    <div className="grid grid-cols-2 gap-2 text-xs sm:grid-cols-4 text-center">
                      <div className="bg-white/5 rounded-xl p-2">
                        <p className="text-[10px] text-zinc-400">Genre</p>
                        <p cla
[truncated — 4525 more characters]
```

### app/api/search/route.ts

```typescript
import { NextResponse } from "next/server";
import { parseNaturalLanguageIntent, searchByIntent } from "@/lib/natural-language-search";

export async function POST(request: Request) { try { const body = await request.json() as { query?: unknown }; if (typeof body.query !== "string" || !body.query.trim()) return NextResponse.json({ profile: {}, recommendations: [] }, { status: 400 }); const query = body.query.trim().slice(0, 500); return NextResponse.json({ profile: parseNaturalLanguageIntent(query), recommendations: searchByIntent(query) }); } catch { return NextResponse.json({ profile: {}, recommendations: [] }, { status: 400 }); } }

```

### app/api/enrich/route.ts

```typescript
import { NextResponse } from "next/server";
import { omdbMetadataProvider } from "@/lib/metadata-provider";
import { localPosterProvider } from "@/lib/poster-provider";

export const runtime = "nodejs";

export async function POST(request: Request) {
  try {
    const body = await request.json() as { titles?: unknown };
    if (!Array.isArray(body.titles)) return NextResponse.json({ details: {} }, { status: 400 });
    const titles = body.titles.filter((title): title is string => typeof title === "string").slice(0, 20);
    const resolved = await Promise.all(titles.map(async (title) => { const details = await omdbMetadataProvider.getByTitle(title, request.signal); if (!details) return [title, null] as const; return [title, { ...details, poster: await localPosterProvider.resolve(title, details.poster, request.signal) }] as const; }));
    return NextResponse.json({ details: Object.fromEntries(resolved.filter(([, details]) => details)) });
  } catch {
    return NextResponse.json({ details: {} });
  }
}

```

### app/api/question/route.ts

```typescript
import { NextResponse } from "next/server";
import { shouldEndConversation } from "@/lib/conversation-manager";
import { generateAdaptiveQuestion } from "@/lib/question-generator";
import type { Answers } from "@/types";

export const runtime = "nodejs";
export async function POST(request: Request) { try { const body = await request.json() as { answers?: Answers; asked?: string[]; confidence?: number }; if (!body.answers || !Array.isArray(body.asked) || typeof body.confidence !== "number") return NextResponse.json({ question: null, confidence: 0, complete: true }, { status: 400 }); const answered = Object.values(body.answers).filter((values) => values.length > 0).length; if (shouldEndConversation(answered, body.confidence)) return NextResponse.json({ question: null, confidence: body.confidence, complete: true }); const question = await generateAdaptiveQuestion(body.answers, body.asked, request.signal); return NextResponse.json({ question, confidence: body.confidence, complete: !question }); } catch { return NextResponse.json({ question: null, confidence: 0, complete: true }); } }

```

### next.config.ts

```typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = { images: { remotePatterns: [{ protocol: "https", hostname: "m.media-amazon.com" }] } };

export default nextConfig;

```

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