Fully static, offline Netflix-style movie site. Browses a local catalog and plays local video files. No server, no build step at runtime — open index.html directly or serve with any static file server.
- Profile select splash
- Hero billboard for featured title
- Genre rows with horizontal carousel
- Detail modal (synopsis, cast, My List toggle)
- Fullscreen video player with resume, seek, keyboard shortcuts
- Live search
- My List (persisted to localStorage)
- Binance design system tokens (dark canvas + yellow accent + light footer)
| Layer | Tech |
|---|---|
| UI | Vanilla ES6 modules, imperative DOM |
| Routing | Hash router (#/browse, #/title/m001, etc.) |
| State | In-memory catalog + localStorage (profile, My List, positions) |
| Styles | TailwindCSS CLI (compiled to dist/styles.css) |
| Tests | node:test (pure modules only) |
index.html HTML shell
src/
main.js Bootstrap, route wiring
catalog.js Pure query functions (getById, search, rowsByGenre…)
router.js parseHash() + startRouter()
store.js localStorage wrapper (profile, My List, positions)
input.css Tailwind directives + component classes
components/
navbar.js Top nav with search
hero.js Billboard hero
card.js Poster card with fallback
row.js Horizontal carousel
modal.js Detail overlay
player.js Fullscreen video player
views/
profiles.js Profile select
browse.js Home (hero + rows)
detail.js Modal over browse
watch.js Player view
search.js Search results grid
genre.js Genre grid
myList.js Saved titles grid
data/
movies.json Catalog (genres + movies)
assets/
img/poster/ Poster images (2:3 ratio)
img/backdrop/ Backdrop images
video/ Video files (.mp4)
fonts/ Optional .woff2 fonts (Inter, JetBrains Mono)
scripts/
validate-data.js Catalog + asset path validator
tests/
catalog.test.js
router.test.js
validate-data.test.js
npm install
npm run build
# Then serve with any static server, e.g.:
npx serve .
# or
python3 -m http.server 8000Open http://localhost:8000.
- Drop poster images into
assets/img/poster/<id>.jpg(2:3 ratio recommended) - Drop backdrop images into
assets/img/backdrop/<id>.jpg(16:9 ratio recommended) - Drop video files into
assets/video/<filename>.mp4 - Edit
data/movies.json— add genres and movie entries following the existing schema - Run
npm run validateto check all paths resolve - Run
npm run buildto rebuild CSS
{
"id": "m001",
"title": "My Movie",
"year": 2024,
"genres": ["action"],
"rating": "PG-13",
"runtime": 120,
"synopsis": "A short description.",
"cast": ["Actor One", "Actor Two"],
"poster": "assets/img/poster/m001.jpg",
"backdrop": "assets/img/backdrop/m001.jpg",
"video": "assets/video/m001.mp4",
"featured": false
}Set "featured": true on one movie to display it as the hero on the browse page.
npm run build # Compile Tailwind CSS → dist/styles.css
npm run watch # Watch mode for CSS
npm run validate # Validate data/movies.json + asset paths
npm test # Run unit tests (catalog, router, validate-data)| Key | Action |
|---|---|
Space |
Play / Pause |
← |
Seek back 10s |
→ |
Seek forward 10s |
M |
Toggle mute |
F |
Fullscreen |
Esc |
Back to browse |
Drop variable font files into assets/fonts/:
Inter-Variable.woff2— body font (maps tofont-nova)JetBrainsMono-Variable.woff2— monospace numbers (maps tofont-plex)
System font stacks are used as fallback if files are absent.
| Hash | View |
|---|---|
#/ |
Redirect (profiles or browse) |
#/profiles |
Profile select |
#/browse |
Home |
#/genre/<slug> |
Genre grid |
#/title/<id> |
Detail modal |
#/watch/<id> |
Player |
#/search?q=<query> |
Search results |
#/my-list |
Saved titles |