Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const Navbar = () => {
<>
<nav className="fixed top-4 left-1/2 transform -translate-x-1/2 w-[95%] max-w-6xl px-6 py-3 flex items-center justify-between bg-black/50 backdrop-blur-md rounded-xl shadow-lg z-40">
<div className="flex items-center gap-3">
<img src="/logo.png" alt="RedPill" className="w-8 h-8 rounded-sm" />
<img src="/top.svg" alt="RedPill" className="w-8 h-8 rounded-sm" />
<Link to="/" className="text-lg font-semibold text-white">RedPill</Link>
</div>

Expand Down
80 changes: 64 additions & 16 deletions frontend/src/pages/Landing.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
import { Link, useNavigate } from "react-router-dom";
import { isLoggedIn } from "../utils/auth";
import { getTrendingFilms, getTopRatedFilms, getGenres } from "../api/films";
Expand Down Expand Up @@ -171,29 +172,44 @@ export default function Landing() {
.finally(() => setLoadingReviews(false));
}, []);

const featured = useMemo(() => {
const m = trending?.[0];
if (!m) {
return {
// Carousel state for featured movies
const [carouselIdx, setCarouselIdx] = useState(0);
const featuredMovies = useMemo(() => {
if (trending && trending.length > 0) {
return trending.slice(0, 4).map((m) => ({
title: m.title,
overview: m.overview,
year: yearOf(m),
genres: m.genre_names || [],
id: m.id,
backdrop: img.backdrop(m.backdrop_path),
}));
}
// fallback movies
return [
{
title: "Her",
overview:
"A sensitive and soulful man earns a living by writing personal letters for other people. Left heartbroken, Theodore develops an unlikely relationship with a newly purchased operating system.",
year: 2013,
genres: ["Drama", "Romance", "Sci-Fi"],
id: 152601,
backdrop: img.backdrop(null),
};
}
return {
title: m.title,
overview: m.overview,
year: yearOf(m),
genres: [],
id: m.id,
backdrop: img.backdrop(m.backdrop_path),
};
},
];
}, [trending]);

// Auto-advance carousel every 5s
useEffect(() => {
if (featuredMovies.length <= 1) return;
const interval = setInterval(() => {
setCarouselIdx((idx) => (idx + 1) % featuredMovies.length);
}, 5000);
return () => clearInterval(interval);
}, [featuredMovies.length]);

const featured = featuredMovies[carouselIdx] || featuredMovies[0];

const handleSearch = () => {
if (searchTerm.trim()) {
navigate(`/search?query=${encodeURIComponent(searchTerm.trim())}`);
Expand All @@ -203,10 +219,42 @@ export default function Landing() {
return (
<div className="min-h-screen bg-white text-gray-900 dark:bg-black dark:text-gray-200">
{/* HERO */}

<section className="relative w-full h-[380px] xs:h-[420px] sm:h-[460px] md:h-[520px] overflow-hidden">
<img src={featured.backdrop} alt={featured.title} className="absolute inset-0 w-full h-full object-cover" style={{ filter: "brightness(0.6)" }} />
<img src={featured.backdrop} alt={featured.title} className="absolute inset-0 w-full h-full object-cover transition-all duration-700" style={{ filter: "brightness(0.6)" }} />
<div className="absolute inset-0 bg-gradient-to-b from-white/0 dark:from-black/10 via-black/50 to-black/80" />

{/* Carousel controls */}
{featuredMovies.length > 1 && (
<>
<button
className="absolute left-4 top-1/2 -translate-y-1/2 z-20 bg-black/40 hover:bg-black/70 text-white rounded-full p-2"
onClick={() => setCarouselIdx((idx) => (idx - 1 + featuredMovies.length) % featuredMovies.length)}
aria-label="Previous featured movie"
>
<FaChevronLeft size={22} />
</button>
<button
className="absolute right-4 top-1/2 -translate-y-1/2 z-20 bg-black/40 hover:bg-black/70 text-white rounded-full p-2"
onClick={() => setCarouselIdx((idx) => (idx + 1) % featuredMovies.length)}
aria-label="Next featured movie"
>
<FaChevronRight size={22} />
</button>
{/* Dots */}
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 flex gap-2 z-20">
{featuredMovies.map((m, i) => (
<button
key={m.id}
className={`h-2 w-2 rounded-full ${i === carouselIdx ? 'bg-yellow-400' : 'bg-white/40'} border border-white/20`}
onClick={() => setCarouselIdx(i)}
aria-label={`Go to featured movie ${i+1}`}
/>
))}
</div>
</>
)}

<motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6 }} className="relative z-10 w-full max-w-7xl mx-auto px-3 xs:px-4 sm:px-6 h-full flex flex-col justify-center">
<div className="max-w-2xl">
<span className="inline-block text-[10px] xs:text-xs font-semibold uppercase tracking-wider text-white bg-black/40 border border-white/20 px-2 py-1 rounded mb-2">
Expand All @@ -215,7 +263,7 @@ export default function Landing() {

<h1 className="text-2xl xs:text-3xl sm:text-4xl md:text-5xl font-extrabold text-white tracking-tight">{featured.title}</h1>

<p className="text-white/80 mt-2 text-xs xs:text-sm sm:text-base">{featured.year} {featured.genres?.length ? <> {featured.genres.join(", ")}</> : null}</p>
<p className="text-white/80 mt-2 text-xs xs:text-sm sm:text-base">{featured.year} {featured.genres?.length ? <> {featured.genres.join(", ")}</> : null}</p>

<p className="text-white/80 mt-3 leading-relaxed line-clamp-4 text-sm xs:text-base">{featured.overview}</p>

Expand Down