home page improved

This commit is contained in:
Tejas Panchal
2025-07-31 22:28:03 +05:30
parent 18e36d8b63
commit 8ee1ff1754
14 changed files with 371 additions and 270 deletions

View File

@@ -16,6 +16,8 @@
"@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2", "@fortawesome/react-fontawesome": "^0.2.2",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@vercel/analytics": "^1.5.0",
"@vercel/speed-insights": "^1.2.0",
"artplayer": "^5.2.3", "artplayer": "^5.2.3",
"artplayer-plugin-chapter": "^1.0.0", "artplayer-plugin-chapter": "^1.0.0",
"artplayer-plugin-hls-control": "^1.0.1", "artplayer-plugin-hls-control": "^1.0.1",

View File

@@ -1,6 +1,8 @@
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { useEffect } from "react"; import { useEffect } from "react";
import { Routes, Route } from "react-router-dom"; import { Routes, Route } from "react-router-dom";
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/react';
import { HomeInfoProvider } from "./context/HomeInfoContext"; import { HomeInfoProvider } from "./context/HomeInfoContext";
import Home from "./pages/Home/Home"; import Home from "./pages/Home/Home";
import AnimeInfo from "./pages/animeInfo/AnimeInfo"; import AnimeInfo from "./pages/animeInfo/AnimeInfo";
@@ -71,6 +73,8 @@ function App() {
</Routes> </Routes>
{!isSplashScreen && <Footer />} {!isSplashScreen && <Footer />}
</main> </main>
<Analytics />
<SpeedInsights />
</div> </div>
</HomeInfoProvider> </HomeInfoProvider>
); );

View File

@@ -7,79 +7,132 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
background: linear-gradient( background: linear-gradient(
to top, 180deg,
rgba(18, 18, 18, 1) 0%, rgba(0, 0, 0, 0) 0%,
rgba(18, 18, 18, 0.7) 20%, rgba(0, 0, 0, 0.4) 50%,
rgba(18, 18, 18, 0) 60%, rgba(0, 0, 0, 0.95) 100%
rgba(18, 18, 18, 0.2) 100%
); );
z-index: 50; z-index: 50;
transition: all 0.3s ease-in-out; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 16px;
} }
.group:hover .overlay { .group:hover .overlay {
background: linear-gradient( background: linear-gradient(
to top, 180deg,
rgba(18, 18, 18, 0.95) 0%, rgba(0, 0, 0, 0.2) 0%,
rgba(18, 18, 18, 0.8) 50%, rgba(0, 0, 0, 0.6) 50%,
rgba(18, 18, 18, 0.6) 100% rgba(0, 0, 0, 0.98) 100%
); );
} }
.dot { .dot {
width: 3px; width: 2px;
height: 3px; height: 2px;
display: inline-block; display: inline-block;
border-radius: 50%; border-radius: 50%;
background: rgba(255, 255, 255, 0.4); background: rgba(255, 255, 255, 0.4);
margin: 0 4px; margin: 0 6px;
position: relative; position: relative;
top: -1px; top: -1px;
} }
/* Modern Card Styles */ /* Modern Card Styles */
.category-card-container { .category-card-container {
transition: transform 0.3s ease-in-out; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
border-radius: 16px;
overflow: hidden;
background: #1a1a1a;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
} }
.category-card-container:hover { .category-card-container:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.category-card-container img {
border-radius: 16px;
transform: scale(1.01);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
filter: brightness(0.95);
}
.category-card-container:hover img {
transform: scale(1.05);
filter: brightness(1.05);
} }
.category-badge { .category-badge {
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.15);
padding: 4px 8px; padding: 4px 8px;
border-radius: 4px; border-radius: 4px;
font-weight: 600; font-weight: 500;
letter-spacing: 0.02em; letter-spacing: 0.02em;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.category-badge:hover { .category-badge:hover {
background: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.2);
} }
.item-title { .item-title {
font-weight: 500; font-weight: 600;
letter-spacing: 0.01em; letter-spacing: 0.01em;
transition: all 0.2s ease; transition: all 0.3s ease;
font-size: 15px;
line-height: 1.4;
color: rgba(255, 255, 255, 0.95);
} }
.item-title:hover { .item-title:hover {
color: #ffffff; color: #ffffff;
text-shadow: 0 0 20px rgba(255, 255, 255, 0.2);
} }
.play-icon { .play-icon {
opacity: 0; opacity: 0;
transform: translate(-50%, -50%) scale(0.8); transform: translate(-50%, -50%) scale(0.9);
transition: all 0.3s ease; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.4));
} }
.group:hover .play-icon { .group:hover .play-icon {
opacity: 1; opacity: 1;
transform: translate(-50%, -50%) scale(1); transform: translate(-50%, -50%) scale(1);
} }
.info-container {
padding: 12px 16px 16px;
background: linear-gradient(
to top,
rgba(0, 0, 0, 0.95) 0%,
rgba(0, 0, 0, 0.8) 50%,
rgba(0, 0, 0, 0) 100%
);
position: absolute;
bottom: 0;
left: 0;
right: 0;
transition: all 0.3s ease;
}
.group:hover .info-container {
padding-bottom: 20px;
background: linear-gradient(
to top,
rgba(0, 0, 0, 0.98) 0%,
rgba(0, 0, 0, 0.85) 50%,
rgba(0, 0, 0, 0) 100%
);
}
.meta-info {
color: rgba(255, 255, 255, 0.7);
font-size: 13px;
font-weight: 500;
}

View File

@@ -87,19 +87,20 @@ const CategoryCard = React.memo(
}; };
return ( return (
<div className={`w-full ${className}`}> <div className={`w-full ${className}`}>
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-8">
<h1 className="font-bold text-2xl text-white max-[478px]:text-[18px] capitalize tracking-wide"> <h1 className="font-semibold text-2xl text-white max-[478px]:text-[18px] capitalize tracking-wide">
{label} {label}
</h1> </h1>
{showViewMore && ( {showViewMore && (
<Link <Link
to={`/${path}`} to={`/${path}`}
className="flex w-fit items-baseline h-fit rounded-3xl gap-x-1 group" className="flex items-center gap-x-1 py-1 px-2 -mr-2 rounded-md
text-[13px] font-medium text-[#ffffff80] hover:text-white
transition-all duration-300 group"
> >
<p className="text-gray-300 text-[12px] font-medium h-fit leading-0 group-hover:text-white transition-all ease-out"> View all
View more <FaChevronRight className="text-[10px] transform transition-transform duration-300
</p> group-hover:translate-x-0.5" />
<FaChevronRight className="text-gray-300 text-[10px] group-hover:text-white transition-all ease-out" />
</Link> </Link>
)} )}
</div> </div>
@@ -115,12 +116,12 @@ const CategoryCard = React.memo(
{itemsToRender.firstRow.map((item, index) => ( {itemsToRender.firstRow.map((item, index) => (
<div <div
key={index} key={index}
className="flex flex-col transition-transform duration-300 ease-in-out category-card-container" className="flex flex-col category-card-container"
style={{ height: "fit-content" }} style={{ height: "fit-content" }}
ref={(el) => (cardRefs.current[index] = el)} ref={(el) => (cardRefs.current[index] = el)}
> >
<div <div
className="w-full relative group hover:cursor-pointer" className="w-full relative group hover:cursor-pointer overflow-hidden rounded-xl"
onClick={() => onClick={() =>
navigate( navigate(
`${ `${
@@ -139,22 +140,22 @@ const CategoryCard = React.memo(
className="text-[40px] text-white absolute top-1/2 left-1/2 play-icon z-[10000]" className="text-[40px] text-white absolute top-1/2 left-1/2 play-icon z-[10000]"
/> />
)} )}
<div className="overlay"></div> <div className="overlay"></div>
<div className="overflow-hidden"> <div className="overflow-hidden rounded-xl">
<img <img
src={`${item.poster}`} src={`${item.poster}`}
alt={item.title} alt={item.title}
className={`w-full h-[320px] object-cover max-[1200px]:h-[35vw] max-[758px]:h-[45vw] max-[478px]:h-[60vw] group-hover:blur-[7px] transform transition-all duration-300 ease-in-out ultra-wide:h-[400px] ${cardStyle}`} className={`w-full h-[320px] object-cover max-[1200px]:h-[35vw] max-[758px]:h-[45vw] max-[478px]:h-[60vw] transform transition-all duration-300 ease-in-out ultra-wide:h-[400px] ${cardStyle}`}
/> />
</div> </div>
{(item.tvInfo?.rating === "18+" || {(item.tvInfo?.rating === "18+" ||
item?.adultContent === true) && ( item?.adultContent === true) && (
<div className="text-white px-2 py-1 rounded-md bg-black/50 backdrop-blur-sm border border-white/10 absolute top-2 left-2 flex items-center justify-center text-[12px] font-medium"> <div className="text-white px-3 py-1 rounded-lg bg-black/40 backdrop-blur-md border border-white/10 absolute top-3 left-3 flex items-center justify-center text-[12px] font-medium">
18+ 18+
</div> </div>
)} )}
<div className="absolute left-2 bottom-3 flex items-center justify-center w-fit space-x-2 z-[100] max-[270px]:flex-col max-[270px]:gap-y-[3px]"> <div className="info-container">
<div className="flex items-center justify-start w-fit space-x-2 z-[100] max-[270px]:flex-col max-[270px]:gap-y-[3px]">
{item.tvInfo?.sub && ( {item.tvInfo?.sub && (
<div className="category-badge flex space-x-1 justify-center items-center"> <div className="category-badge flex space-x-1 justify-center items-center">
<FontAwesomeIcon <FontAwesomeIcon
@@ -185,6 +186,27 @@ const CategoryCard = React.memo(
</div> </div>
)} )}
</div> </div>
<Link
to={`/${item.id}`}
className="text-white font-medium mt-3 item-title hover:text-white hover:cursor-pointer line-clamp-1 block"
>
{language === "EN" ? item.title : item.japanese_title}
</Link>
<div className="flex items-center gap-x-2 w-full mt-2 overflow-hidden">
<div className="text-gray-300 text-[13px] text-nowrap overflow-hidden text-ellipsis font-medium">
{item.tvInfo.showType.split(" ").shift()}
</div>
<div className="dot"></div>
<div className="text-gray-300 text-[13px] text-nowrap overflow-hidden text-ellipsis font-medium">
{item.tvInfo?.duration === "m" ||
item.tvInfo?.duration === "?" ||
item.duration === "m" ||
item.duration === "?"
? "N/A"
: item.tvInfo?.duration || item.duration || "N/A"}
</div>
</div>
</div>
{hoveredItem === item.id + index && {hoveredItem === item.id + index &&
window.innerWidth > 1024 && ( window.innerWidth > 1024 && (
<div <div
@@ -198,31 +220,11 @@ const CategoryCard = React.memo(
</div> </div>
)} )}
</div> </div>
<Link
to={`/${item.id}`}
className="text-gray-100 font-medium mt-2 item-title hover:text-white hover:cursor-pointer line-clamp-1"
>
{language === "EN" ? item.title : item.japanese_title}
</Link>
{item.description && ( {item.description && (
<div className="line-clamp-3 text-[13px] font-light text-gray-400 mt-1 max-[1200px]:hidden"> <div className="line-clamp-3 text-[13px] font-light text-gray-400 mt-3 max-[1200px]:hidden">
{item.description} {item.description}
</div> </div>
)} )}
<div className="flex items-center gap-x-2 w-full mt-2 overflow-hidden">
<div className="text-gray-500 text-[13px] text-nowrap overflow-hidden text-ellipsis font-medium">
{item.tvInfo.showType.split(" ").shift()}
</div>
<div className="dot"></div>
<div className="text-gray-500 text-[13px] text-nowrap overflow-hidden text-ellipsis font-medium">
{item.tvInfo?.duration === "m" ||
item.tvInfo?.duration === "?" ||
item.duration === "m" ||
item.duration === "?"
? "N/A"
: item.tvInfo?.duration || item.duration || "N/A"}
</div>
</div>
</div> </div>
))} ))}
</div> </div>

View File

@@ -46,10 +46,10 @@ const ContinueWatching = () => {
</div> </div>
<div className="flex gap-x-2 pr-2 max-[350px]:hidden"> <div className="flex gap-x-2 pr-2 max-[350px]:hidden">
<button className="btn-prev bg-gray-700 text-white p-3 rounded-full hover:bg-gray-500 transition max-[768px]:p-2"> <button className="continue-btn-prev bg-gray-700 text-white p-3 rounded-full hover:bg-gray-500 transition max-[768px]:p-2">
<FaChevronLeft className="text-xs" /> <FaChevronLeft className="text-xs" />
</button> </button>
<button className="btn-next bg-gray-700 text-white p-3 rounded-full hover:bg-gray-500 transition max-[768px]:p-2"> <button className="continue-btn-next bg-gray-700 text-white p-3 rounded-full hover:bg-gray-500 transition max-[768px]:p-2">
<FaChevronRight className="text-xs" /> <FaChevronRight className="text-xs" />
</button> </button>
</div> </div>
@@ -70,11 +70,11 @@ const ContinueWatching = () => {
}} }}
modules={[Navigation]} modules={[Navigation]}
navigation={{ navigation={{
nextEl: ".btn-next", nextEl: ".continue-btn-next",
prevEl: ".btn-prev", prevEl: ".continue-btn-prev",
}} }}
> >
{memoizedWatchList.map((item, index) => ( {memoizedWatchList.slice().reverse().map((item, index) => (
<SwiperSlide <SwiperSlide
key={index} key={index}
className="text-center flex justify-center items-center" className="text-center flex justify-center items-center"

View File

@@ -114,28 +114,28 @@ const Schedule = () => {
return ( return (
<> <>
<div className="w-full mt-[60px] max-[480px]:mt-[40px]"> <div className="w-full mt-8 max-[480px]:mt-6">
<div className="flex items-center justify-between max-[570px]:flex-col max-[570px]:items-start max-[570px]:gap-y-2"> <div className="flex items-center justify-between max-[570px]:flex-col max-[570px]:items-start max-[570px]:gap-y-2">
<div className="font-bold text-2xl text-[#ffbade] max-[478px]:text-[18px]"> <div className="font-bold text-2xl text-white max-[478px]:text-[18px]">
Estimated Schedule Estimated Schedule
</div> </div>
<p className="leading-[28px] px-[10px] bg-white text-black rounded-full my-[6px] text-[16px] font-bold max-[478px]:text-[12px] max-[275px]:text-[10px]"> <p className="leading-[28px] px-3 bg-zinc-800 text-white rounded-md text-[14px] font-medium max-[478px]:text-[12px] max-[275px]:text-[10px]">
({GMTOffset}) {currentTime.toLocaleDateString()}{" "} ({GMTOffset}) {currentTime.toLocaleDateString()}{" "}
{currentTime.toLocaleTimeString()} {currentTime.toLocaleTimeString()}
</p> </p>
</div> </div>
</div> </div>
<div className="w-full overflow-x-scroll space-x-4 scrollbar-hide pt-10 px-6 max-[480px]:px-4 max-[478px]:pt-4"> <div className="w-full overflow-x-scroll space-x-4 scrollbar-hide pt-6 px-4 max-[480px]:px-2 max-[478px]:pt-4">
<div className="relative w-full"> <div className="relative w-full">
<Swiper <Swiper
slidesPerView={3} slidesPerView={3}
spaceBetween={2} spaceBetween={2}
breakpoints={{ breakpoints={{
250: { slidesPerView: 3, spaceBetween: 10 }, 250: { slidesPerView: 3, spaceBetween: 8 },
640: { slidesPerView: 4, spaceBetween: 10 }, 640: { slidesPerView: 4, spaceBetween: 8 },
768: { slidesPerView: 5, spaceBetween: 10 }, 768: { slidesPerView: 5, spaceBetween: 8 },
1024: { slidesPerView: 7, spaceBetween: 10 }, 1024: { slidesPerView: 7, spaceBetween: 8 },
1300: { slidesPerView: 7, spaceBetween: 15 }, 1300: { slidesPerView: 7, spaceBetween: 8 },
}} }}
modules={[Pagination, Navigation]} modules={[Pagination, Navigation]}
navigation={{ navigation={{
@@ -150,20 +150,20 @@ const Schedule = () => {
<div <div
ref={(el) => (cardRefs.current[index] = el)} ref={(el) => (cardRefs.current[index] = el)}
onClick={() => toggleActive(index)} onClick={() => toggleActive(index)}
className={`h-[70px] flex flex-col justify-center items-center w-full text-center rounded-xl shadow-lg cursor-pointer ${ className={`h-[60px] flex flex-col justify-center items-center w-full text-center rounded-lg cursor-pointer transition-all duration-200 ${
currentActiveIndex === index currentActiveIndex === index
? "bg-[#ffbade] text-black" ? "bg-white text-black"
: "bg-white bg-opacity-5 text-[#ffffff] hover:bg-[#373646] transition-all duration-300 ease-in-out" : "bg-zinc-800 text-white hover:bg-zinc-700"
}`} }`}
> >
<div className="text-[18px] font-bold max-[400px]:text-[14px] max-[350px]:text-[12px]"> <div className="text-[16px] font-bold max-[400px]:text-[14px] max-[350px]:text-[12px]">
{date.dayname} {date.dayname}
</div> </div>
<div <div
className={`text-[14px] max-[400px]:text-[12px] ${ className={`text-[13px] max-[400px]:text-[11px] ${
currentActiveIndex === index currentActiveIndex === index
? "text-black" ? "text-zinc-800"
: "text-gray-400" : "text-zinc-400"
} max-[350px]:text-[10px]`} } max-[350px]:text-[10px]`}
> >
{date.monthName} {date.day} {date.monthName} {date.day}
@@ -172,28 +172,28 @@ const Schedule = () => {
</SwiperSlide> </SwiperSlide>
))} ))}
</Swiper> </Swiper>
<button className="next absolute top-1/2 right-[-15px] transform -translate-y-1/2 flex justify-center items-center cursor-pointer"> <button className="next absolute top-1/2 right-[-12px] transform -translate-y-1/2 flex justify-center items-center cursor-pointer">
<FaChevronRight className="text-[12px]" /> <FaChevronRight className="text-[12px]" />
</button> </button>
<button className="prev absolute top-1/2 left-[-15px] transform -translate-y-1/2 flex justify-center items-center cursor-pointer"> <button className="prev absolute top-1/2 left-[-12px] transform -translate-y-1/2 flex justify-center items-center cursor-pointer">
<FaChevronLeft className="text-[12px]" /> <FaChevronLeft className="text-[12px]" />
</button> </button>
</div> </div>
</div> </div>
{loading ? ( {loading ? (
<div className="w-full h-[70px] flex justify-center items-center"> <div className="w-full h-[60px] flex justify-center items-center">
<BouncingLoader /> <BouncingLoader />
</div> </div>
) : !scheduleData || scheduleData.length === 0 ? ( ) : !scheduleData || scheduleData.length === 0 ? (
<div className="w-full h-[70px] flex justify-center items-center mt-5 text-xl"> <div className="w-full h-[60px] flex justify-center items-center mt-4 text-lg text-zinc-400">
No data to display No data to display
</div> </div>
) : error ? ( ) : error ? (
<div className="w-full h-[70px] flex justify-center items-center mt-5 text-xl"> <div className="w-full h-[60px] flex justify-center items-center mt-4 text-lg text-zinc-400">
Something went wrong Something went wrong
</div> </div>
) : ( ) : (
<div className="flex flex-col mt-5 items-start"> <div className="flex flex-col mt-4 items-start">
{(showAll {(showAll
? scheduleData ? scheduleData
: Array.isArray(scheduleData) : Array.isArray(scheduleData)
@@ -203,31 +203,31 @@ const Schedule = () => {
<Link <Link
to={`/${item.id}`} to={`/${item.id}`}
key={idx} key={idx}
className="w-full flex justify-between py-4 border-[#FFFFFF0D] border-b-[1px] group cursor-pointer max-[325px]:py-2" className="w-full flex justify-between py-3 border-zinc-800 border-b-[1px] group cursor-pointer hover:bg-zinc-900/50 px-2 transition-all duration-200 max-[325px]:py-2"
> >
<div className="flex items-center max-w-[500px] gap-x-7 max-[400px]:gap-x-2"> <div className="flex items-center max-w-[500px] gap-x-4 max-[400px]:gap-x-2">
<div className="text-lg font-semibold text-[#ffffff59] group-hover:text-[#ffbade] transition-all duration-300 ease-in-out max-[600px]:text-[14px] max-[275px]:text-[12px]"> <div className="text-base font-medium text-zinc-500 group-hover:text-white transition-all duration-200 max-[600px]:text-[14px] max-[275px]:text-[12px]">
{item.time || "N/A"} {item.time || "N/A"}
</div> </div>
<h3 className="text-[17px] font-semibold line-clamp-1 group-hover:text-[#ffbade] transition-all duration-300 ease-in-out max-[600px]:text-[14px] max-[275px]:text-[12px]"> <h3 className="text-[16px] font-medium line-clamp-1 group-hover:text-white transition-all duration-200 max-[600px]:text-[14px] max-[275px]:text-[12px]">
{item.title || "N/A"} {item.title || "N/A"}
</h3> </h3>
</div> </div>
<button className="max-w-[150px] flex items-center py-1 px-4 rounded-lg gap-x-2 group-hover:bg-[#ffbade] transition-all duration-300 ease-in-out"> <div className="flex items-center gap-x-2 py-1 px-3 rounded-md bg-zinc-800 group-hover:bg-white transition-all duration-200">
<FontAwesomeIcon <FontAwesomeIcon
icon={faPlay} icon={faPlay}
className="mt-[1px] text-[10px] max-[320px]:text-[8px] group-hover:text-black transition-all duration-300 ease-in-out" className="mt-[1px] text-[10px] max-[320px]:text-[8px] text-zinc-400 group-hover:text-black"
/> />
<p className="text-[14px] text-white group-hover:text-black transition-all duration-300 ease-in-out max-[275px]:text-[12px]"> <p className="text-[13px] text-zinc-400 group-hover:text-black max-[275px]:text-[12px]">
Episode {item.episode_no || "N/A"} EP {item.episode_no || "N/A"}
</p> </p>
</button> </div>
</Link> </Link>
))} ))}
{scheduleData.length > 7 && ( {scheduleData.length > 7 && (
<button <button
onClick={toggleShowAll} onClick={toggleShowAll}
className="text-white py-4 hover:text-[#ffbade] font-semibold transition-all duration-300 ease-in-out max-sm:text-[13px]" className="text-zinc-400 py-3 hover:text-white font-medium transition-all duration-200 max-sm:text-[13px]"
> >
{showAll ? "Show Less" : "Show More"} {showAll ? "Show Less" : "Show More"}
</button> </button>

View File

@@ -1,11 +1,18 @@
.next, .next,
.prev { .prev {
width: 30px; width: 24px;
height: 30px; height: 24px;
border-radius: 100%; border-radius: 6px;
background-color: white; background-color: rgb(39 39 42);
color: black; color: rgb(161 161 170);
font-size: 13px; font-size: 12px;
padding: 10px; padding: 6px;
z-index: 10; z-index: 10;
transition: all 0.2s;
}
.next:hover,
.prev:hover {
background-color: rgb(63 63 70);
color: white;
} }

View File

@@ -8,6 +8,8 @@ import "./Sidebar.css";
const MENU_ITEMS = [ const MENU_ITEMS = [
{ name: "Home", path: "/home", icon: faHome }, { name: "Home", path: "/home", icon: faHome },
{ name: "Recently Added", path: "/recently-added", icon: faCirclePlay },
{ name: "Top Upcoming", path: "/top-upcoming", icon: faFilePen },
{ name: "Subbed Anime", path: "/subbed-anime", icon: faFilePen }, { name: "Subbed Anime", path: "/subbed-anime", icon: faFilePen },
{ name: "Dubbed Anime", path: "/dubbed-anime", icon: faPlay }, { name: "Dubbed Anime", path: "/dubbed-anime", icon: faPlay },
{ name: "Most Popular", path: "/most-popular", icon: faFire }, { name: "Most Popular", path: "/most-popular", icon: faFire },

View File

@@ -0,0 +1,72 @@
import { useState } from "react";
import PropTypes from "prop-types";
import CategoryCard from "@/src/components/categorycard/CategoryCard.jsx";
import { Link } from "react-router-dom";
import { FaChevronRight } from "react-icons/fa";
function TabbedAnimeSection({ topAiring, mostFavorite, latestCompleted, className = "" }) {
const [activeTab, setActiveTab] = useState("airing");
const tabs = [
{ id: "airing", label: "Top Airing", data: topAiring, path: "top-airing" },
{ id: "favorite", label: "Most Favorite", data: mostFavorite, path: "most-favorite" },
{ id: "completed", label: "Latest Completed", data: latestCompleted, path: "completed" },
];
const activeTabData = tabs.find((tab) => tab.id === activeTab);
return (
<div className={`w-full ${className}`}>
<div className="flex justify-between items-center border-b border-[#ffffff1a] relative">
<div className="flex">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`relative px-6 py-4 text-[15px] font-medium transition-all duration-300
${activeTab === tab.id
? "text-white after:absolute after:bottom-0 after:left-0 after:w-full after:h-[2px] after:bg-primary after:rounded-t-full"
: "text-[#ffffff80] hover:text-white"
}
before:absolute before:bottom-0 before:left-1/2 before:w-0 before:h-[2px] before:bg-[#ffffff40]
before:transition-all before:duration-300 before:-translate-x-1/2
hover:before:w-full
group
`}
>
<span className="relative z-10 transition-transform duration-300 group-hover:transform group-hover:translate-y-[-1px]">
{tab.label}
</span>
</button>
))}
</div>
<Link
to={`/${activeTabData.path}`}
className="flex items-center gap-x-1 py-1 px-2 -mr-2 rounded-md
text-[13px] font-medium text-[#ffffff80] hover:text-white
transition-all duration-300 group"
>
View all
<FaChevronRight className="text-[10px] transform transition-transform duration-300
group-hover:translate-x-0.5" />
</Link>
</div>
<CategoryCard
data={activeTabData.data}
path={activeTabData.path}
limit={12}
showViewMore={false}
/>
</div>
);
}
TabbedAnimeSection.propTypes = {
topAiring: PropTypes.array.isRequired,
mostFavorite: PropTypes.array.isRequired,
latestCompleted: PropTypes.array.isRequired,
className: PropTypes.string,
};
export default TabbedAnimeSection;

View File

@@ -49,19 +49,17 @@ function Topten({ data, className }) {
}; };
return ( return (
<div className={`flex flex-col space-y-6 ${className}`}> <div className={`flex flex-col space-y-4 ${className}`}>
<div className="flex justify-between items-center max-[350px]:flex-col max-[350px]:gap-y-2 max-[350px]:items-start"> <div className="flex justify-between items-center max-[350px]:flex-col max-[350px]:gap-y-2 max-[350px]:items-start">
<h1 className="font-bold text-2xl text-[#ffbade]">Top 10</h1> <h1 className="font-bold text-2xl text-white tracking-tight">Top 10</h1>
<ul className="flex justify-between w-fit bg-[#373646] rounded-[4px] text-sm font-bold"> <ul className="flex justify-between w-fit bg-[#1a1a1a] rounded-lg overflow-hidden shadow-lg">
{["today", "week", "month"].map((period) => ( {["today", "week", "month"].map((period) => (
<li <li
key={period} key={period}
className={`cursor-pointer p-2 px-3 ${ className={`cursor-pointer p-1.5 px-4 transition-all duration-200 ${
activePeriod === period activePeriod === period
? "bg-[#ffbade] text-[#555462]" ? "bg-white text-black font-medium"
: "text-white hover:text-[#ffbade]" : "text-gray-400 hover:text-white hover:bg-[#2a2a2a]"
} ${period === "today" ? "rounded-l-[4px]" : ""} ${
period === "month" ? "rounded-r-[4px]" : ""
}`} }`}
onClick={() => handlePeriodChange(period)} onClick={() => handlePeriodChange(period)}
> >
@@ -71,19 +69,19 @@ function Topten({ data, className }) {
</ul> </ul>
</div> </div>
<div className="flex flex-col space-y-4 bg-[#2B2A3C] p-4 pt-8"> <div className="flex flex-col space-y-3 bg-[#1a1a1a] p-3 pt-6 rounded-lg shadow-lg">
{currentData && {currentData &&
currentData.map((item, index) => ( currentData.map((item, index) => (
<div <div
key={index} key={index}
className="flex items-center gap-x-4" className="flex items-center gap-x-3 group"
ref={(el) => (cardRefs.current[index] = el)} ref={(el) => (cardRefs.current[index] = el)}
> >
<h1 <h1
className={`font-bold text-2xl ${ className={`font-bold text-2xl transition-colors ${
index < 3 index < 3
? "pb-1 text-white border-b-[3px] border-[#ffbade]" ? "text-white border-b-2 border-white pb-0.5"
: "text-[#777682]" : "text-gray-600"
} max-[350px]:hidden`} } max-[350px]:hidden`}
> >
{`${index + 1 < 10 ? "0" : ""}${index + 1}`} {`${index + 1 < 10 ? "0" : ""}${index + 1}`}
@@ -92,16 +90,15 @@ function Topten({ data, className }) {
style={{ style={{
borderBottom: borderBottom:
index + 1 < 10 index + 1 < 10
? "1px solid rgba(255, 255, 255, .075)" ? "1px solid rgba(255, 255, 255, .1)"
: "none", : "none",
}} }}
className="flex pb-4 relative container items-center" className="flex pb-3 relative container items-center group-hover:bg-[#2a2a2a] transition-colors duration-200 rounded-lg p-1.5"
> >
{/* Image with tooltip behavior */}
<img <img
src={`${item.poster}`} src={`${item.poster}`}
alt={item.title} alt={item.title}
className="w-[60px] h-[75px] rounded-md object-cover flex-shrink-0 cursor-pointer" className="w-[55px] h-[70px] rounded-lg object-cover flex-shrink-0 cursor-pointer shadow-md transition-transform duration-200 group-hover:scale-[1.02]"
onClick={() => navigate(`/watch/${item.id}`)} onClick={() => navigate(`/watch/${item.id}`)}
onMouseEnter={() => handleMouseEnter(item, index)} onMouseEnter={() => handleMouseEnter(item, index)}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
@@ -132,33 +129,33 @@ function Topten({ data, className }) {
</div> </div>
)} )}
<div className="flex flex-col ml-4 space-y-2"> <div className="flex flex-col ml-3 space-y-1.5">
<Link <Link
to={`/${item.id}`} to={`/${item.id}`}
className="text-[1em] font-[500] hover:cursor-pointer hover:text-[#ffbade] transform transition-all ease-out line-clamp-1 max-[478px]:line-clamp-2 max-[478px]:text-[14px]" className="text-[0.95em] font-medium text-gray-200 hover:text-white transform transition-all ease-out line-clamp-1 max-[478px]:line-clamp-2 max-[478px]:text-[14px]"
onClick={() => handleNavigate(item.id)} onClick={() => handleNavigate(item.id)}
> >
{language === "EN" ? item.title : item.japanese_title} {language === "EN" ? item.title : item.japanese_title}
</Link> </Link>
<div className="flex flex-wrap items-center w-fit space-x-1 max-[350px]:gap-y-[3px]"> <div className="flex flex-wrap items-center w-fit space-x-2 max-[350px]:gap-y-[3px]">
{item.tvInfo?.sub && ( {item.tvInfo?.sub && (
<div className="flex space-x-1 justify-center items-center bg-[#B0E3AF] rounded-[4px] px-[4px] text-black py-[2px]"> <div className="flex space-x-1 justify-center items-center bg-white bg-opacity-10 backdrop-blur-sm rounded-md px-1.5 py-0.5 transition-colors duration-200 hover:bg-opacity-20">
<FontAwesomeIcon <FontAwesomeIcon
icon={faClosedCaptioning} icon={faClosedCaptioning}
className="text-[12px]" className="text-[11px] text-gray-300"
/> />
<p className="text-[12px] font-bold"> <p className="text-[11px] font-medium text-gray-300">
{item.tvInfo.sub} {item.tvInfo.sub}
</p> </p>
</div> </div>
)} )}
{item.tvInfo?.dub && ( {item.tvInfo?.dub && (
<div className="flex space-x-1 justify-center items-center bg-[#B9E7FF] rounded-[4px] px-[8px] text-black py-[2px]"> <div className="flex space-x-1 justify-center items-center bg-white bg-opacity-10 backdrop-blur-sm rounded-md px-1.5 py-0.5 transition-colors duration-200 hover:bg-opacity-20">
<FontAwesomeIcon <FontAwesomeIcon
icon={faMicrophone} icon={faMicrophone}
className="text-[12px]" className="text-[11px] text-gray-300"
/> />
<p className="text-[12px] font-bold"> <p className="text-[11px] font-medium text-gray-300">
{item.tvInfo.dub} {item.tvInfo.dub}
</p> </p>
</div> </div>

View File

@@ -1,74 +1,79 @@
import { Pagination, Navigation } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
import { useLanguage } from "@/src/context/LanguageContext"; import { useLanguage } from "@/src/context/LanguageContext";
import { Link, useNavigate } from "react-router-dom"; import { Link } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faClosedCaptioning,
faMicrophone,
faFire
} from "@fortawesome/free-solid-svg-icons";
const Trending = ({ trending }) => { const Trending = ({ trending, className }) => {
const { language } = useLanguage(); const { language } = useLanguage();
const navigate = useNavigate();
return ( return (
<div className="mt-6 max-[1200px]:px-4 max-md:px-0"> <div className={`bg-[#141414] rounded-lg p-6 ${className}`}>
<h1 className="text-[#ffbade] text-2xl font-bold max-md:pl-4"> <div className="flex items-center gap-2 mb-4">
Trending <FontAwesomeIcon icon={faFire} className="text-white/90" />
</h1> <h2 className="text-xl font-semibold text-white">Trending Now</h2>
<div className="pr-[60px] relative mx-auto overflow-hidden z-[1] mt-6 max-[759px]:pr-0"> </div>
<Swiper <div className="flex flex-col space-y-2 max-h-[600px] overflow-y-auto pr-2 scrollbar-thin scrollbar-track-[#1a1a1a] scrollbar-thumb-[#2a2a2a] hover:scrollbar-thumb-[#333] scrollbar-thumb-rounded">
className="w-full h-full"
slidesPerView={3}
spaceBetween={2}
breakpoints={{
479: { spaceBetween: 15 },
575: { spaceBetween: 15 },
640: { slidesPerView: 3, spaceBetween: 15 },
900: { slidesPerView: 4, spaceBetween: 15 },
1300: { slidesPerView: 6, spaceBetween: 15 },
}}
modules={[Pagination, Navigation]}
navigation={{
nextEl: ".btn-next",
prevEl: ".btn-prev",
}}
>
{trending && {trending &&
trending.map((item, idx) => ( trending.map((item, index) => (
<SwiperSlide <div key={index} className="group">
key={idx}
className="text-center flex text-[18px] justify-center items-center"
onClick={() => navigate(`/watch/${item.id}`)}
>
<div className="w-full h-auto pb-[115%] relative inline-block overflow-hidden max-[575px]:pb-[150%]">
<div className="absolute left-0 top-0 bottom-0 overflow-hidden w-[40px] text-center font-semibold bg-[#201F31] max-[575px]:top-0 max-[575px]:h-[30px] max-[575px]:z-[9] max-[575px]:bg-white">
<span className="absolute left-0 right-0 bottom-0 text-[24px] leading-[1.1em] text-center z-[9] transform -rotate-90 max-[575px]:transform max-[575px]:rotate-0 max-[575px]:text-[#111] max-[575px]:text-[18px] max-[575px]:leading-[30px]">
{item.number}
</span>
<div className="w-[150px] h-fit text-left transform -rotate-90 absolute bottom-[100px] left-[-55px] leading-[40px] text-ellipsis whitespace-nowrap overflow-hidden text-white text-[16px] font-medium">
{language === "EN" ? item.title : item.japanese_title}
</div>
</div>
<Link <Link
to={`/${item.id}`} to={`/${item.id}`}
className="inline-block bg-[#2a2c31] absolute w-auto left-[40px] right-0 top-0 bottom-0 max-[575px]:left-0 max-[575px]:top-0 max-[575px]:bottom-0" onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
className="block"
> >
<div className="flex items-start gap-3 p-2 rounded-lg transition-colors hover:bg-[#1a1a1a]">
<div className="relative">
<img <img
src={`${item.poster}`} src={item.poster}
alt={item.title} alt={item.title}
className="block w-full h-full object-cover hover:cursor-pointer" className="w-[50px] h-[70px] rounded object-cover"
title={item.title}
/> />
<div className="absolute top-0 left-0 bg-white/90 text-black text-xs font-bold px-1.5 rounded-br">
#{index + 1}
</div>
</div>
<div className="flex flex-col gap-1.5 flex-1 min-w-0">
<span className="text-sm font-medium text-gray-200 group-hover:text-white transition-colors line-clamp-2">
{language === "EN" ? item.title : item.japanese_title}
</span>
<div className="flex flex-wrap items-center gap-2">
{item.tvInfo?.sub && (
<div className="flex items-center gap-1 px-1.5 py-0.5 bg-[#2a2a2a] rounded text-gray-300">
<FontAwesomeIcon
icon={faClosedCaptioning}
className="text-[10px]"
/>
<span className="text-[10px] font-medium">
{item.tvInfo.sub}
</span>
</div>
)}
{item.tvInfo?.dub && (
<div className="flex items-center gap-1 px-1.5 py-0.5 bg-[#2a2a2a] rounded text-gray-300">
<FontAwesomeIcon
icon={faMicrophone}
className="text-[10px]"
/>
<span className="text-[10px] font-medium">
{item.tvInfo.dub}
</span>
</div>
)}
{item.tvInfo?.showType && (
<span className="text-xs text-gray-400">
{item.tvInfo.showType}
</span>
)}
</div>
</div>
</div>
</Link> </Link>
</div> </div>
</SwiperSlide>
))} ))}
</Swiper>
<div className="absolute top-0 right-0 bottom-0 w-[45px] flex flex-col space-y-2 max-[759px]:hidden">
<div className="btn-next bg-[#383747] h-[50%] flex justify-center items-center rounded-[8px] cursor-pointer transition-all duration-300 ease-out hover:bg-[#ffbade] hover:text-[#383747]">
<FaChevronRight />
</div>
<div className="btn-prev bg-[#383747] h-[50%] flex justify-center items-center rounded-[8px] cursor-pointer transition-all duration-300 ease-out hover:bg-[#ffbade] hover:text-[#383747]">
<FaChevronLeft />
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -1,7 +1,6 @@
import website_name from "@/src/config/website.js"; import website_name from "@/src/config/website.js";
import Spotlight from "@/src/components/spotlight/Spotlight.jsx"; import Spotlight from "@/src/components/spotlight/Spotlight.jsx";
import Trending from "@/src/components/trending/Trending.jsx"; import Trending from "@/src/components/trending/Trending.jsx";
import Cart from "@/src/components/cart/Cart.jsx";
import CategoryCard from "@/src/components/categorycard/CategoryCard.jsx"; import CategoryCard from "@/src/components/categorycard/CategoryCard.jsx";
import Genre from "@/src/components/genres/Genre.jsx"; import Genre from "@/src/components/genres/Genre.jsx";
import Topten from "@/src/components/topten/Topten.jsx"; import Topten from "@/src/components/topten/Topten.jsx";
@@ -10,6 +9,7 @@ import Error from "@/src/components/error/Error.jsx";
import { useHomeInfo } from "@/src/context/HomeInfoContext.jsx"; import { useHomeInfo } from "@/src/context/HomeInfoContext.jsx";
import Schedule from "@/src/components/schedule/Schedule"; import Schedule from "@/src/components/schedule/Schedule";
import ContinueWatching from "@/src/components/continue/ContinueWatching"; import ContinueWatching from "@/src/components/continue/ContinueWatching";
import TabbedAnimeSection from "@/src/components/tabbed-anime/TabbedAnimeSection";
function Home() { function Home() {
const { homeInfo, homeInfoLoading, error } = useHomeInfo(); const { homeInfo, homeInfoLoading, error } = useHomeInfo();
@@ -24,56 +24,27 @@ function Home() {
<Genre data={homeInfo.genres} /> <Genre data={homeInfo.genres} />
</div> </div>
<ContinueWatching /> <ContinueWatching />
<Trending trending={homeInfo.trending} />
<div className="mt-10 flex gap-6 max-[1200px]:px-4 max-[1200px]:grid max-[1200px]:grid-cols-2 max-[1200px]:mt-12 max-[1200px]:gap-y-10 max-[680px]:grid-cols-1">
<Cart
label="Top Airing"
data={homeInfo.top_airing}
path="top-airing"
/>
<Cart
label="Most Popular"
data={homeInfo.most_popular}
path="most-popular"
/>
<Cart
label="Most Favorite"
data={homeInfo.most_favorite}
path="most-favorite"
/>
<Cart
label="Latest Completed"
data={homeInfo.latest_completed}
path="completed"
/>
</div>
<div className="w-full grid grid-cols-[minmax(0,75%),minmax(0,25%)] gap-x-6 max-[1200px]:flex flex-col max-[1200px]:px-4"> <div className="w-full grid grid-cols-[minmax(0,75%),minmax(0,25%)] gap-x-6 max-[1200px]:flex flex-col max-[1200px]:px-4">
<div> <div>
<CategoryCard <CategoryCard
label="Latest Episode" label="Latest Episode"
data={homeInfo.latest_episode} data={homeInfo.latest_episode}
className={"mt-[60px]"} className="mt-[60px]"
path="recently-updated" path="recently-updated"
limit={12} limit={12}
/> />
<CategoryCard <Schedule className="mt-8" />
label={`New On ${website_name}`} <TabbedAnimeSection
data={homeInfo.recently_added} topAiring={homeInfo.top_airing}
className={"mt-[60px]"} mostFavorite={homeInfo.most_favorite}
path="recently-added" latestCompleted={homeInfo.latest_completed}
limit={12} className="mt-8"
/>
<Schedule />
<CategoryCard
label="Top Upcoming"
data={homeInfo.top_upcoming}
className={"mt-[30px]"}
path="top-upcoming"
limit={12}
/> />
</div> </div>
<div className="w-full mt-[60px]"> <div className="w-full mt-[60px]">
<Topten data={homeInfo.topten} className={"mt-12"} /> <Trending trending={homeInfo.trending} />
<Topten data={homeInfo.topten} className="mt-12" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -51,17 +51,6 @@ function Category({ path, label }) {
return ( return (
<div className="w-full flex flex-col gap-y-4 mt-[64px] max-md:mt-[50px]"> <div className="w-full flex flex-col gap-y-4 mt-[64px] max-md:mt-[50px]">
<div className="w-full flex gap-x-4 items-center bg-[#191826] p-5 max-[575px]:px-3 max-[320px]:hidden">
<img
src="https://media.tenor.com/hJfxLKzDUFcAAAAM/bleach-best-anime.gif"
alt="Share Anime"
className="w-[60px] h-auto rounded-full max-[1024px]:w-[40px] max-[575px]:hidden"
/>
<div className="flex flex-col w-fit">
<p className="text-[15px] font-bold text-[#FFBADE]">Share Anime</p>
<p className="text-[16px] text-white">to your friends</p>
</div>
</div>
{categoryInfo ? ( {categoryInfo ? (
<div className="w-full px-4 grid grid-cols-[minmax(0,75%),minmax(0,25%)] gap-x-6 max-[1200px]:flex max-[1200px]:flex-col max-[1200px]:gap-y-10"> <div className="w-full px-4 grid grid-cols-[minmax(0,75%),minmax(0,25%)] gap-x-6 max-[1200px]:flex max-[1200px]:flex-col max-[1200px]:gap-y-10">
{page > totalPages ? ( {page > totalPages ? (

View File

@@ -9,17 +9,14 @@ import IframePlayer from "@/src/components/player/IframePlayer";
import Episodelist from "@/src/components/episodelist/Episodelist"; import Episodelist from "@/src/components/episodelist/Episodelist";
import website_name from "@/src/config/website"; import website_name from "@/src/config/website";
import Sidecard from "@/src/components/sidecard/Sidecard"; import Sidecard from "@/src/components/sidecard/Sidecard";
import CategoryCard from "@/src/components/categorycard/CategoryCard";
import { import {
faClosedCaptioning, faClosedCaptioning,
faMicrophone, faMicrophone,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Servers from "@/src/components/servers/Servers"; import Servers from "@/src/components/servers/Servers";
import CategoryCardLoader from "@/src/components/Loader/CategoryCard.loader";
import { Skeleton } from "@/src/components/ui/Skeleton/Skeleton"; import { Skeleton } from "@/src/components/ui/Skeleton/Skeleton";
import SidecardLoader from "@/src/components/Loader/Sidecard.loader"; import SidecardLoader from "@/src/components/Loader/Sidecard.loader";
import Voiceactor from "@/src/components/voiceactor/Voiceactor";
import Watchcontrols from "@/src/components/watchcontrols/Watchcontrols"; import Watchcontrols from "@/src/components/watchcontrols/Watchcontrols";
import useWatchControl from "@/src/hooks/useWatchControl"; import useWatchControl from "@/src/hooks/useWatchControl";
import Player from "@/src/components/player/Player"; import Player from "@/src/components/player/Player";