A full-stack Movie & TV tracker with AI-powered recommendations and a Liquid-Glass UI.
Overview · Features · Architecture · Recommendation Engine · Getting Started · API
CineVision lets you browse movies and TV shows from TMDB, curate a personal watchlist backed by Supabase, and receive content-based recommendations from a Flask + scikit-learn service. The interface is a cohesive Liquid-Glass design system: one shared navigation component, a reusable media-card frame, a cinematic video landing screen, and a coverflow search whose blurred backdrop tracks the focused result.
| Frontend | React 19 SPA (Vite) with a token-driven glass design system, framer-motion animations, and full responsiveness. |
| Backend | Flask API that builds a TF-IDF model over a TMDB-seeded catalog and ranks recommendations by cosine similarity. |
| Data | Supabase for auth & watchlist storage; TMDB for movie/TV metadata and imagery. |
|
|
graph LR
subgraph client["Client · React 19 + Vite"]
UI["Pages · Coverflow Search"]
NAV["Shared Sidebar"]
end
subgraph services["Cloud Services"]
SUPA["Supabase<br/>Auth + Watchlist DB"]
TMDB["TMDB API<br/>Movies · TV · Images"]
end
subgraph api["Flask API · Python"]
REC["Recommender<br/>TF-IDF + Cosine"]
ART["Artifacts Cache<br/>parquet · npz · pkl"]
end
UI -->|"search · details"| TMDB
UI -->|"auth · watchlist"| SUPA
UI -->|"POST /recommend"| REC
REC -->|"seed catalog (cold start)"| TMDB
REC <-->|"load · save"| ART
The React app talks to TMDB for metadata, Supabase for auth and watchlist persistence, and the Flask service for recommendations. On a cold start the recommender seeds its catalog from TMDB and caches the fitted model so subsequent requests are fast.
Content-based filtering over a catalog seeded from TMDB's most popular movies and shows. Each title is turned into a text document (title, overview, genres, keywords, cast, directors/creators), vectorized with TF-IDF, and compared with cosine similarity.
flowchart TD
A["Watchlist seeds<br/>media_type + media_id"] --> B{"Match seeds to<br/>catalog rows"}
B -->|"matched"| C["Weight each seed<br/>by rating (vote_average)"]
C --> D["Sum weighted TF-IDF vectors<br/>into one query vector"]
D --> E["Cosine similarity<br/>vs. full catalog matrix"]
E --> F["Drop seeds<br/>+ optional media-type filter"]
F --> G["Rank · take Top-K"]
G --> H["Return results JSON"]
B -->|"no match"| Z["Return empty list"]
A typical recommendation request end-to-end:
sequenceDiagram
actor U as User
participant R as React App
participant S as Supabase
participant F as Flask API
participant T as TMDB
U->>R: Open Recommendations
R->>S: Fetch watchlist (user_id)
S-->>R: Watchlist items
R->>F: POST /recommend { watchlist, limit }
Note over F,T: Cold start seeds catalog from TMDB, then caches artifacts
F->>F: TF-IDF + cosine similarity
F-->>R: Ranked results
R-->>U: Recommendation rows
The backend runs on Render's free tier, which spins the service down after ~15 minutes of inactivity. The first request after idle then pays a cold start — measured ~67s cold vs. ~0.15s warm. Two lightweight mechanisms keep recommendations snappy:
- Warm-up ping — the frontend fires
GET /healthzon app load (App.jsx), so the backend starts waking while the user browses and signs in, instead of blocking their firstPOST /recommend. - Keep-alive — an external uptime monitor pings
/healthzevery ~10 minutes so the service never sleeps, keeping even a first-time visitor's request fast.
sequenceDiagram
participant U as User
participant A as Frontend (App.jsx)
participant C as Uptime monitor
participant B as Backend (Render free)
Note over C,B: Keep-alive — ping every ~10 min prevents spin-down
loop every ~10 minutes
C->>B: GET /healthz
B-->>C: 200 ok
end
U->>A: Open the app
A->>B: GET /healthz (warm-up on load)
Note over B: cold start begins early, overlaps with browsing
B-->>A: 200 ok
U->>A: Add to watchlist / open Recommendations
A->>B: POST /recommend
B-->>A: ~150 ms - already warm
Set up the keep-alive (free, ~2 minutes):
- Create a free account at cron-job.org (or UptimeRobot).
- Add a cronjob → URL
https://cinevision.onrender.com/healthz, methodGET, schedule every 10 minutes (*/10 * * * *). - Save & enable.
Render's free web service allows ~750 instance-hours/month — enough to keep one backend awake 24/7 (~720h). The frontend is a static site and doesn't count against that.
| Layer | Technologies |
|---|---|
| Frontend | React 19, Vite 7, React Router, Framer Motion, Supabase JS, Axios |
| Backend | Python 3.11+, Flask 3, Flask-CORS, Gunicorn |
| ML / Data | scikit-learn (TF-IDF, linear_kernel), SciPy sparse, pandas, NumPy, PyArrow |
| Services | Supabase (Auth + Postgres), TMDB API |
| Design | Liquid-Glass tokens, Orbitron · Oxanium · Outfit · Inter |
CineVision/
├── frontend/
│ ├── public/ # Static assets (videos, images, SVGs)
│ ├── src/
│ │ ├── styles/ # Modular CSS system
│ │ │ ├── design-system.css # Tokens, resets, utilities, animations
│ │ │ ├── components.css # Reusable component styles (sidebar, cards…)
│ │ │ ├── layouts.css # Page layouts (auth, dashboard, detail, intro)
│ │ │ ├── search.css # Coverflow search page
│ │ │ ├── responsive.css # All breakpoints (imported last)
│ │ │ └── index.css # CSS entry point (@imports the above)
│ │ ├── components/
│ │ │ └── Sidebar.jsx # Shared navigation used by every page
│ │ ├── main.jsx # App entry — mounts React & loads global styles
│ │ ├── App.jsx # Router & route definitions
│ │ ├── Animations.jsx # Cinematic landing / intro screen
│ │ ├── DashBoard.jsx # Home page
│ │ ├── Movies.jsx # Movies browse
│ │ ├── Shows.jsx # TV shows browse
│ │ ├── Search.jsx # Coverflow search
│ │ ├── Detail.jsx # Movie/show detail
│ │ ├── Watchlist.jsx # User watchlist
│ │ ├── Recommendation.jsx # AI recommendations
│ │ ├── Login.jsx · Signup.jsx # Auth screens
│ │ ├── ForgotPassword.jsx · UpdatePassword.jsx
│ │ ├── Footer.jsx # Global footer
│ │ └── supabaseClient.js # Supabase config
│ ├── .env.example
│ └── package.json
│
├── backend/
│ ├── app.py # Flask API (healthz · recommend · rebuild)
│ ├── recommender.py # TF-IDF + cosine recommendation engine
│ ├── requirements.txt # Python dependencies
│ └── artifacts/ # Cached model (gitignored)
│
├── .gitignore · LICENSE · README.md
The landing screen leads to auth; once signed in, the shared sidebar cross-links every core page, and each listing routes into the shared detail view.
graph TD
INTRO["/ · Landing"] --> LOGIN["/login"]
INTRO --> SIGNUP["/signup"]
LOGIN --> DASH["/dashboard"]
SIGNUP --> DASH
DASH <--> MOV["/movies"]
DASH <--> SHOW["/shows"]
DASH <--> SEARCH["/search"]
DASH <--> REC["/recommendation"]
DASH <--> WATCH["/watchlist"]
MOV --> DET["/detail/:type/:id"]
SHOW --> DET
SEARCH --> DET
WATCH --> DET
REC --> DET
Prerequisites — Node.js 18+, Python 3.11+, a Supabase project, and a TMDB API key.
1. Clone
git clone https://github.com/NikanEidi/CineVision.git
cd CineVision2. Frontend
cd frontend
npm installCreate frontend/.env:
VITE_SUPABASE_URL=your_supabase_project_url
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key
VITE_TMDB_API_KEY=your_tmdb_api_key
VITE_API_BASE=http://127.0.0.1:5178npm run devTo use Sign in as Guest, enable Anonymous sign-ins in Supabase under Authentication → Providers.
3. Backend
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtCreate backend/.env:
TMDB_API_KEY=your_tmdb_api_keypython app.py # dev — serves on http://0.0.0.0:5178
# production: gunicorn -w 2 -b 0.0.0.0:5178 app:app4. Open — the frontend runs at http://localhost:5173.
| Method | Endpoint | Description |
|---|---|---|
GET |
/healthz |
Health check; ensures the model is loaded/built |
POST |
/recommend |
Ranked recommendations from a watchlist |
POST |
/rebuild |
Clear artifacts and rebuild the catalog from TMDB |
POST /recommend
// Response
{
"results": [
{
"media_type": "movie",
"id": 807,
"title": "Se7en",
"poster_path": "/6yoghtyTpznpBik8EngEmJskVUO.jpg",
"vote_average": 8.4,
"similarity": 0.42,
"genres": "Crime, Mystery, Thriller"
}
]
}CSS is layered and token-driven; import order is enforced by index.css:
@import './design-system.css'; /* Tokens, resets, utilities, animations */
@import './components.css'; /* Reusable components (sidebar, cards…) */
@import './layouts.css'; /* Page structures (auth, dashboard, intro)*/
@import './search.css'; /* Coverflow search */
@import './responsive.css'; /* Breakpoints — MUST be last */:root {
/* Color */
--color-primary: #5F099E;
--color-accent-cyan: #00E5FF;
--bg-primary: #0A0A0F;
/* Glass */
--glass-bg-medium: rgba(255, 255, 255, 0.08);
--blur-md: blur(16px);
/* Typography */
--font-display: 'Orbitron', 'Outfit', sans-serif;
--font-heading: 'Oxanium', 'Outfit', sans-serif;
--font-body: 'Inter', 'Outfit', system-ui, sans-serif;
}| Breakpoint | Width | Behavior |
|---|---|---|
| Mobile SM | < 480px | Single column, icon-only nav |
| Mobile | 480–599px | Compact cards |
| Mobile LG | 600–767px | Stacked actions |
| Tablet | 768–1023px | Sidebar collapses to bottom bar |
| Tablet LG | 1024–1199px | Sidebar returns |
| Desktop | 1200–1599px | Full layout |
| Desktop XL | 1600px+ | Max content width |
v1.1
- Unified navigation — one shared
Sidebarcomponent; every page renders an identical nav bar and a correct mobile bottom bar. - Restored landing screen — re-added the missing intro styles (video hero, glass sound toggle, Sign In / Sign Up), fully responsive.
- Search backdrop — the coverflow's blurred background tracks the focused result with a smooth crossfade over a neutral dark base; tightened vertical spacing.
- Cleaner pipeline — global styles imported once from
main.jsx; changelog-style comments removed; ESLint made JSX-aware.
This product uses the TMDB API but is not endorsed or certified by TMDB. Movie and TV data provided by The Movie Database.
Nikan Eidi — full-stack developer specializing in AI/ML-powered web applications.
Released under the MIT License — see LICENSE for details.