mirror of
https://github.com/myronblair/kino-app
synced 2026-07-29 14:02:50 -05:00
auto-commit for 0175172f-4f7e-4804-ad7f-68312dcdad0d
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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}`)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user