auto-commit for 0175172f-4f7e-4804-ad7f-68312dcdad0d

This commit is contained in:
emergent-agent-e1
2026-07-23 22:23:41 +00:00
parent 22d8479b39
commit bd8480bd14
11 changed files with 89 additions and 31 deletions
+4 -4
View File
@@ -17,7 +17,7 @@ export const Hero = ({ movie, onPlay, onMore, onAddList }) => {
<div className="relative h-full max-w-[1500px] mx-auto px-6 md:px-12 flex flex-col justify-end pb-24 md:pb-32 fade-up">
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E] mb-4" data-testid="hero-eyebrow">
Featured
In the Spotlight
</span>
<h1 className="font-display text-5xl md:text-7xl font-black tracking-tighter leading-none text-white max-w-3xl"
data-testid="hero-title">
@@ -51,7 +51,7 @@ export const Hero = ({ movie, onPlay, onMore, onAddList }) => {
data-testid="hero-play-button"
>
<Play size={18} fill="white" strokeWidth={0} />
<span className="text-sm uppercase tracking-[0.2em] font-medium">Play</span>
<span className="text-sm uppercase tracking-[0.2em] font-medium">Watch</span>
</button>
<button
onClick={() => onMore?.(movie)}
@@ -59,7 +59,7 @@ export const Hero = ({ movie, onPlay, onMore, onAddList }) => {
data-testid="hero-more-info-button"
>
<Info size={18} strokeWidth={1.5} />
<span className="text-sm uppercase tracking-[0.2em] font-medium">More Info</span>
<span className="text-sm uppercase tracking-[0.2em] font-medium">Details</span>
</button>
<button
onClick={() => onAddList?.(movie)}
@@ -67,7 +67,7 @@ export const Hero = ({ movie, onPlay, onMore, onAddList }) => {
data-testid="hero-add-list-button"
>
<Plus size={18} strokeWidth={1.5} />
<span className="text-xs uppercase tracking-[0.2em]">My List</span>
<span className="text-xs uppercase tracking-[0.2em]">Save</span>
</button>
</div>
</div>
+4 -4
View File
@@ -30,11 +30,11 @@ export const MovieDetailModal = ({ movie, open, onClose, onWatchlistChange }) =>
if (inList) {
await api.delete(`/watchlist/${movie.id}`);
setInList(false);
toast.success("Removed from My List");
toast.success("Removed from the shelf");
} else {
await api.post(`/watchlist/${movie.id}`);
setInList(true);
toast.success("Added to My List");
toast.success("Saved to your shelf");
}
onWatchlistChange?.();
} catch {
@@ -87,7 +87,7 @@ export const MovieDetailModal = ({ movie, open, onClose, onWatchlistChange }) =>
data-testid="modal-play-button"
>
<Play size={16} fill="white" strokeWidth={0} />
<span className="text-sm uppercase tracking-[0.2em] font-medium">Play</span>
<span className="text-sm uppercase tracking-[0.2em] font-medium">Watch</span>
</button>
<button
onClick={toggle}
@@ -95,7 +95,7 @@ export const MovieDetailModal = ({ movie, open, onClose, onWatchlistChange }) =>
data-testid="modal-watchlist-button"
>
{inList ? <Check size={16} strokeWidth={1.5} /> : <Plus size={16} strokeWidth={1.5} />}
<span className="text-xs uppercase tracking-[0.2em]">{inList ? "In My List" : "My List"}</span>
<span className="text-xs uppercase tracking-[0.2em]">{inList ? "On the Shelf" : "Save to Shelf"}</span>
</button>
</div>
</div>
+4 -4
View File
@@ -34,10 +34,10 @@ export const Navbar = () => {
</Link>
{user && (
<nav className="hidden md:flex items-center gap-7">
<NavLink to="/browse" className={linkClass} data-testid="nav-browse">Browse</NavLink>
<NavLink to="/shows" className={linkClass} data-testid="nav-shows">Shows</NavLink>
<NavLink to="/my-list" className={linkClass} data-testid="nav-my-list">My List</NavLink>
<NavLink to="/requests" className={linkClass} data-testid="nav-requests">Requests</NavLink>
<NavLink to="/browse" className={linkClass} data-testid="nav-browse">Library</NavLink>
<NavLink to="/shows" className={linkClass} data-testid="nav-shows">Series</NavLink>
<NavLink to="/my-list" className={linkClass} data-testid="nav-my-list">Shelf</NavLink>
<NavLink to="/requests" className={linkClass} data-testid="nav-requests">Wishlist</NavLink>
{user.is_admin && <NavLink to="/admin" className={linkClass} data-testid="nav-admin">Admin</NavLink>}
</nav>
)}
+4 -4
View File
@@ -58,12 +58,12 @@ export default function Browse() {
<div className="-mt-20 relative z-10 pb-24">
{continueWatching.length > 0 && (
<Row title="Continue Watching" movies={continueWatching} onCardClick={handleMore} progressMap={progressMap} />
<Row title="Resume" movies={continueWatching} onCardClick={handleMore} progressMap={progressMap} />
)}
<Row title="Trending Now" movies={trending} onCardClick={handleMore} progressMap={progressMap} />
<Row title="New Releases" movies={newReleases} onCardClick={handleMore} progressMap={progressMap} />
<Row title="Popular" movies={trending} onCardClick={handleMore} progressMap={progressMap} />
<Row title="Fresh Arrivals" movies={newReleases} onCardClick={handleMore} progressMap={progressMap} />
{watchlist.length > 0 && (
<Row title="My List" movies={watchlist} onCardClick={handleMore} progressMap={progressMap} />
<Row title="Your Shelf" movies={watchlist} onCardClick={handleMore} progressMap={progressMap} />
)}
{genres.slice(0, 6).map((g) => {
const list = byGenre(g);
+14 -3
View File
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import api, { API, isHls } from "../lib/api";
import api, { API, isHls, getSubtitleUrl } from "../lib/api";
import { ArrowLeft, SkipForward } from "lucide-react";
import Hls from "hls.js";
@@ -10,6 +10,7 @@ export default function EpisodePlayer() {
const [ep, setEp] = useState(null);
const [show, setShow] = useState(null);
const [nextEp, setNextEp] = useState(null);
const [subs, setSubs] = useState([]);
const [showNextCta, setShowNextCta] = useState(false);
const videoRef = useRef(null);
const hlsRef = useRef(null);
@@ -18,9 +19,13 @@ export default function EpisodePlayer() {
useEffect(() => {
let cancelled = false;
(async () => {
const { data: e } = await api.get(`/episodes/${id}`);
const [{ data: e }, { data: subList }] = await Promise.all([
api.get(`/episodes/${id}`),
api.get(`/episodes/${id}/subtitles`).catch(() => ({ data: [] })),
]);
if (cancelled) return;
setEp(e);
setSubs(subList);
const [{ data: s }, { data: siblings }] = await Promise.all([
api.get(`/shows/${e.show_id}`),
api.get(`/shows/${e.show_id}/episodes`),
@@ -78,7 +83,13 @@ export default function EpisodePlayer() {
<p className="text-[10px] uppercase tracking-[0.3em] text-[#8A8A8A]">S{ep.season_number}·E{ep.episode_number} · {ep.title}</p>
</div>
<video ref={videoRef} controls autoPlay className="w-full h-full object-contain bg-black"
onTimeUpdate={onTimeUpdate} onEnded={onEnded} data-testid="ep-player-video" />
onTimeUpdate={onTimeUpdate} onEnded={onEnded} data-testid="ep-player-video">
{subs.map((sub) => (
<track key={sub.id} kind="subtitles" src={getSubtitleUrl(sub)}
srcLang={sub.language} label={sub.label} default={sub.is_default}
data-testid={`ep-player-track-${sub.id}`} />
))}
</video>
{showNextCta && nextEp && (
<button onClick={() => nav(`/watch/episode/${nextEp.id}`)}
+3 -3
View File
@@ -35,12 +35,12 @@ export default function Login() {
/>
<div className="absolute inset-0 bg-gradient-to-r from-[#050505]/60 to-[#050505]" />
<div className="absolute bottom-12 left-12 right-12 z-10">
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Personal Cinema</span>
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Private Screening Room</span>
<h1 className="font-display text-6xl font-black tracking-tighter text-white mt-4 leading-none">
Your library,<br/>your way.
Your reels,<br/>your rules.
</h1>
<p className="text-[#8A8A8A] mt-6 max-w-md leading-relaxed">
Stream the films you own, the way you remember them without the noise.
Every frame you already own, indexed and ready no algorithms, no ads, no interruptions.
</p>
</div>
</div>
+4 -4
View File
@@ -16,17 +16,17 @@ export default function MyList() {
return (
<div className="min-h-screen bg-[#050505] pt-32 pb-24" data-testid="my-list-page">
<div className="px-6 md:px-12 max-w-[1500px] mx-auto">
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Your collection</span>
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Curated for you</span>
<h1 className="font-display text-5xl md:text-6xl font-black tracking-tighter text-white mt-3">
My List
Shelf
</h1>
<p className="text-[#8A8A8A] mt-4 max-w-xl">
Films you've curated for later. Click any to play or remove.
Titles you've set aside for a proper evening. Tap any to start or clear it off the shelf.
</p>
{items.length === 0 ? (
<div className="mt-16 border border-[#222] p-12 text-center" data-testid="my-list-empty">
<p className="text-[#8A8A8A]">Your list is empty. Add films from Browse.</p>
<p className="text-[#8A8A8A]">Nothing on the shelf yet save something from the Library.</p>
</div>
) : (
<div className="mt-12 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6">
+1 -1
View File
@@ -48,7 +48,7 @@ export default function ProfileSelect() {
<div className="min-h-screen bg-[#050505] flex flex-col items-center justify-center px-6 py-16" data-testid="profile-select-page">
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E] mb-6">Welcome back, {user?.name}</span>
<h1 className="font-display text-5xl md:text-6xl font-black tracking-tighter text-white text-center">
Who's watching?
Choose a viewer
</h1>
<div className="mt-16 flex flex-wrap justify-center gap-8 max-w-4xl">
+2 -2
View File
@@ -39,9 +39,9 @@ export default function Register() {
/>
<div className="absolute inset-0 bg-gradient-to-r from-[#050505]/60 to-[#050505]" />
<div className="absolute bottom-12 left-12 right-12 z-10">
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Join Kino</span>
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Open the doors</span>
<h1 className="font-display text-6xl font-black tracking-tighter text-white mt-4 leading-none">
Curate.<br/>Stream.<br/>Repeat.
Collect.<br/>Cue.<br/>Enjoy.
</h1>
</div>
</div>
+2 -2
View File
@@ -39,11 +39,11 @@ export default function Shows() {
<div className="min-h-screen bg-[#050505] pt-32 pb-24" data-testid="shows-page">
<div className="px-6 md:px-12 max-w-[1500px] mx-auto">
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Series</span>
<h1 className="font-display text-5xl md:text-6xl font-black tracking-tighter text-white mt-3">TV Shows</h1>
<h1 className="font-display text-5xl md:text-6xl font-black tracking-tighter text-white mt-3">Your Series</h1>
{continueEps.length > 0 && (
<div className="mt-12">
<h2 className="font-display text-2xl font-bold tracking-tight text-white mb-4">Continue Watching</h2>
<h2 className="font-display text-2xl font-bold tracking-tight text-white mb-4">Resume</h2>
<div className="flex gap-4 overflow-x-auto no-scrollbar pb-4">
{continueEps.map(({ episode, show, progress }) => {
const pct = progress.duration_seconds ? Math.min(100, (progress.position_seconds / progress.duration_seconds) * 100) : 0;