mirror of
https://github.com/JustAnimeCore/JustAnime.git
synced 2026-04-17 22:01:45 +00:00
watch page progress
This commit is contained in:
@@ -137,9 +137,9 @@ function Episodelist({
|
|||||||
}, [activeEpisodeId, episodes]);
|
}, [activeEpisodeId, episodes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col w-full h-full max-[1200px]:max-h-[500px]">
|
<div className="flex flex-col w-full h-full">
|
||||||
<div className="sticky top-0 z-10 flex flex-col gap-y-[5px] justify-start px-4 py-5 bg-[#1a1a1a] border-b border-[#2a2a2a]">
|
<div className="sticky top-0 z-10 flex flex-col gap-y-[5px] justify-start px-4 py-3 bg-[#1a1a1a] border-b border-[#2a2a2a]">
|
||||||
<h1 className="text-[14px] font-semibold text-white mb-2">Episodes</h1>
|
<h1 className="text-[14px] font-semibold text-white mb-1">Episodes</h1>
|
||||||
{totalEpisodes > 100 && (
|
{totalEpisodes > 100 && (
|
||||||
<div className="w-full flex gap-x-4 items-center max-[1200px]:justify-between">
|
<div className="w-full flex gap-x-4 items-center max-[1200px]:justify-between">
|
||||||
<div className="min-w-fit flex text-[13px]">
|
<div className="min-w-fit flex text-[13px]">
|
||||||
@@ -198,7 +198,7 @@ function Episodelist({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div ref={listContainerRef} className="w-full h-full overflow-y-auto bg-[#1a1a1a]">
|
<div ref={listContainerRef} className="w-full flex-1 overflow-y-auto bg-[#1a1a1a]">
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
totalEpisodes > 30
|
totalEpisodes > 30
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import useToolTipPosition from "@/src/hooks/useToolTipPosition";
|
import useToolTipPosition from "@/src/hooks/useToolTipPosition";
|
||||||
import Qtip from "../qtip/Qtip";
|
import Qtip from "../qtip/Qtip";
|
||||||
|
|
||||||
function Sidecard({ data, label, className, limit }) {
|
function Sidecard({ data, label, className }) {
|
||||||
const { language } = useLanguage();
|
const { language } = useLanguage();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showAll, setShowAll] = useState(false);
|
|
||||||
const [hoverTimeout, setHoverTimeout] = useState(null);
|
const [hoverTimeout, setHoverTimeout] = useState(null);
|
||||||
const handleMouseEnter = (item, index) => {
|
const handleMouseEnter = (item, index) => {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@@ -24,116 +23,86 @@ function Sidecard({ data, label, className, limit }) {
|
|||||||
clearTimeout(hoverTimeout);
|
clearTimeout(hoverTimeout);
|
||||||
setHoveredItem(null);
|
setHoveredItem(null);
|
||||||
};
|
};
|
||||||
const toggleShowAll = () => {
|
|
||||||
setShowAll((prev) => !prev);
|
|
||||||
};
|
|
||||||
|
|
||||||
const displayedData = limit
|
|
||||||
? data.slice(0, limit)
|
|
||||||
: showAll
|
|
||||||
? data
|
|
||||||
: data.slice(0, 6);
|
|
||||||
const [hoveredItem, setHoveredItem] = useState(null);
|
const [hoveredItem, setHoveredItem] = useState(null);
|
||||||
const { tooltipPosition, tooltipHorizontalPosition, cardRefs } =
|
const { tooltipPosition, tooltipHorizontalPosition, cardRefs } =
|
||||||
useToolTipPosition(hoveredItem, data);
|
useToolTipPosition(hoveredItem, data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col space-y-6 ${className}`}>
|
<div className={`flex flex-col ${className}`}>
|
||||||
<h1 className="font-bold text-2xl text-[#ffbade]">{label}</h1>
|
<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">
|
||||||
<div className="flex flex-col space-y-4 bg-[#2B2A3C] p-4 pt-8">
|
|
||||||
{data &&
|
{data &&
|
||||||
displayedData.map((item, index) => (
|
data.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center gap-x-4"
|
className="group"
|
||||||
ref={(el) => (cardRefs.current[index] = el)}
|
ref={(el) => (cardRefs.current[index] = el)}
|
||||||
>
|
>
|
||||||
<div
|
<div className="flex items-start gap-3 p-2 rounded-lg transition-colors hover:bg-[#1f1f1f]">
|
||||||
style={{
|
{hoveredItem === item.id + index && window.innerWidth > 1024 && (
|
||||||
borderBottom:
|
<div
|
||||||
index + 1 < displayedData.length
|
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition} ${
|
||||||
? "1px solid rgba(255, 255, 255, .075)"
|
tooltipPosition === "top-1/2"
|
||||||
: "none",
|
? "translate-y-[50px]"
|
||||||
}}
|
: "translate-y-[-50px]"
|
||||||
className="flex pb-4 relative container items-center"
|
} z-[100000] transform transition-all duration-300 ease-in-out ${
|
||||||
>
|
hoveredItem === item.id + index
|
||||||
{hoveredItem === item.id + index &&
|
? "opacity-100 translate-y-0"
|
||||||
window.innerWidth > 1024 && (
|
: "opacity-0 translate-y-2"
|
||||||
<div
|
}`}
|
||||||
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition} ${
|
>
|
||||||
tooltipPosition === "top-1/2"
|
<Qtip id={item.id} />
|
||||||
? "translate-y-[50px]"
|
</div>
|
||||||
: "translate-y-[-50px]"
|
)}
|
||||||
} z-[100000] transform transition-all duration-300 ease-in-out ${
|
|
||||||
hoveredItem === item.id + index
|
|
||||||
? "opacity-100 translate-y-0"
|
|
||||||
: "opacity-0 translate-y-2"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Qtip id={item.id} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
src={`${item.poster}`}
|
||||||
alt={item.title}
|
alt={item.title}
|
||||||
className="flex-shrink-0 w-[60px] h-[75px] rounded-md object-cover cursor-pointer"
|
className="w-[50px] h-[70px] rounded object-cover cursor-pointer transition-transform group-hover:scale-105"
|
||||||
onClick={() => navigate(`/watch/${item.id}`)}
|
onClick={() => navigate(`/watch/${item.id}`)}
|
||||||
onMouseEnter={() => handleMouseEnter(item, index)}
|
onMouseEnter={() => handleMouseEnter(item, index)}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col ml-4 space-y-2">
|
<div className="flex flex-col gap-1.5 flex-1 min-w-0">
|
||||||
<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-sm font-medium text-gray-200 hover:text-white transition-colors line-clamp-1"
|
||||||
onClick={() =>
|
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" })
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{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-[320px]:gap-y-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{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 items-center gap-1 px-1.5 py-0.5 bg-[#2a2a2a] rounded text-gray-300">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faClosedCaptioning}
|
icon={faClosedCaptioning}
|
||||||
className="text-[12px]"
|
className="text-[10px]"
|
||||||
/>
|
/>
|
||||||
<p className="text-[12px] font-bold">
|
<span className="text-[10px] font-medium">
|
||||||
{item.tvInfo.sub}
|
{item.tvInfo.sub}
|
||||||
</p>
|
</span>
|
||||||
</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 items-center gap-1 px-1.5 py-0.5 bg-[#2a2a2a] rounded text-gray-300">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faMicrophone}
|
icon={faMicrophone}
|
||||||
className="text-[12px]"
|
className="text-[10px]"
|
||||||
/>
|
/>
|
||||||
<p className="text-[12px] font-bold">
|
<span className="text-[10px] font-medium">
|
||||||
{item.tvInfo.dub}
|
{item.tvInfo.dub}
|
||||||
</p>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{item.tvInfo?.showType && (
|
{item.tvInfo?.showType && (
|
||||||
<div className="flex items-center gap-x-2">
|
<span className="text-xs text-gray-400">
|
||||||
<div className="dot ml-[4px]"></div>
|
{item.tvInfo.showType}
|
||||||
<p className="text-[15px] font-light">
|
</span>
|
||||||
{item.tvInfo.showType}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{!limit && data.length > 6 && (
|
|
||||||
<button
|
|
||||||
className="w-full bg-[#555462d3] py-3 mt-4 hover:bg-[#555462] rounded-md font-bold transform transition-all ease-out"
|
|
||||||
onClick={toggleShowAll}
|
|
||||||
>
|
|
||||||
{showAll ? "Show less" : "Show more"}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const ToggleButton = ({ label, isActive, onClick }) => (
|
const ToggleButton = ({ label, isActive, onClick }) => (
|
||||||
<button className="flex gap-x-2" onClick={onClick}>
|
<button
|
||||||
<h1 className="capitalize text-[13px]">{label}</h1>
|
className="flex items-center text-xs px-2 py-0.5 rounded transition-colors hover:bg-[#2a2a2a]"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<span className="text-gray-300">{label}</span>
|
||||||
<span
|
<span
|
||||||
className={`capitalize text-[13px] ${
|
className={`ml-1.5 ${
|
||||||
isActive ? "text-[#ffbade]" : "text-red-500"
|
isActive ? "text-white" : "text-gray-500"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isActive ? "on" : "off"}
|
{isActive ? "ON" : "OFF"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -42,25 +45,25 @@ export default function WatchControls({
|
|||||||
}, [episodeId, episodes]);
|
}, [episodeId, episodes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#11101A] w-full flex justify-between flex-wrap px-4 pt-4 max-[1200px]:bg-[#14151A] max-[375px]:flex-col max-[375px]:gap-y-2">
|
<div className="w-full flex justify-between items-center px-3 py-2 border-b border-gray-800">
|
||||||
<div className="flex gap-x-4 flex-wrap">
|
<div className="flex gap-x-2">
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
label="auto play"
|
label="Auto Play"
|
||||||
isActive={autoPlay}
|
isActive={autoPlay}
|
||||||
onClick={() => setAutoPlay((prev) => !prev)}
|
onClick={() => setAutoPlay((prev) => !prev)}
|
||||||
/>
|
/>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
label="auto skip intro"
|
label="Skip Intro"
|
||||||
isActive={autoSkipIntro}
|
isActive={autoSkipIntro}
|
||||||
onClick={() => setAutoSkipIntro((prev) => !prev)}
|
onClick={() => setAutoSkipIntro((prev) => !prev)}
|
||||||
/>
|
/>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
label="auto next"
|
label="Auto Next"
|
||||||
isActive={autoNext}
|
isActive={autoNext}
|
||||||
onClick={() => setAutoNext((prev) => !prev)}
|
onClick={() => setAutoNext((prev) => !prev)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-x-6 max-[575px]:gap-x-4 max-[375px]:justify-end">
|
<div className="flex items-center gap-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentEpisodeIndex > 0) {
|
if (currentEpisodeIndex > 0) {
|
||||||
@@ -70,11 +73,13 @@ export default function WatchControls({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={currentEpisodeIndex <= 0}
|
disabled={currentEpisodeIndex <= 0}
|
||||||
|
className={`w-7 h-7 flex items-center justify-center rounded transition-colors ${
|
||||||
|
currentEpisodeIndex <= 0
|
||||||
|
? "text-gray-600 cursor-not-allowed"
|
||||||
|
: "text-gray-300 hover:text-white"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon icon={faBackward} className="text-[14px]" />
|
||||||
icon={faBackward}
|
|
||||||
className="text-[20px] max-[575px]:text-[16px] text-white"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -85,11 +90,13 @@ export default function WatchControls({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={currentEpisodeIndex >= episodes?.length - 1}
|
disabled={currentEpisodeIndex >= episodes?.length - 1}
|
||||||
|
className={`w-7 h-7 flex items-center justify-center rounded transition-colors ${
|
||||||
|
currentEpisodeIndex >= episodes?.length - 1
|
||||||
|
? "text-gray-600 cursor-not-allowed"
|
||||||
|
: "text-gray-300 hover:text-white"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon icon={faForward} className="text-[14px]" />
|
||||||
icon={faForward}
|
|
||||||
className="text-[20px] max-[575px]:text-[16px] text-white"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ export default function Watch() {
|
|||||||
autoNext,
|
autoNext,
|
||||||
setAutoNext,
|
setAutoNext,
|
||||||
} = useWatchControl();
|
} = useWatchControl();
|
||||||
|
const playerRef = useRef(null);
|
||||||
|
const videoContainerRef = useRef(null);
|
||||||
|
const controlsRef = useRef(null);
|
||||||
|
const episodesRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!episodes || episodes.length === 0) return;
|
if (!episodes || episodes.length === 0) return;
|
||||||
@@ -119,26 +123,66 @@ export default function Watch() {
|
|||||||
}, [streamInfo, episodeId, animeId, totalEpisodes, navigate]);
|
}, [streamInfo, episodeId, animeId, totalEpisodes, navigate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Function to adjust the height of episodes list to match only video + controls
|
||||||
const adjustHeight = () => {
|
const adjustHeight = () => {
|
||||||
if (window.innerWidth > 1200) {
|
if (window.innerWidth > 1200) {
|
||||||
const player = document.querySelector(".player");
|
if (videoContainerRef.current && controlsRef.current && episodesRef.current) {
|
||||||
const episodes = document.querySelector(".episodes");
|
// Calculate combined height of video container and controls
|
||||||
if (player && episodes) {
|
const videoHeight = videoContainerRef.current.offsetHeight;
|
||||||
episodes.style.height = `${player.clientHeight}px`;
|
const controlsHeight = controlsRef.current.offsetHeight;
|
||||||
|
const totalHeight = videoHeight + controlsHeight;
|
||||||
|
|
||||||
|
// Apply the combined height to episodes container
|
||||||
|
episodesRef.current.style.height = `${totalHeight}px`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const episodes = document.querySelector(".episodes");
|
if (episodesRef.current) {
|
||||||
if (episodes) {
|
episodesRef.current.style.height = 'auto';
|
||||||
episodes.style.height = "auto";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
adjustHeight();
|
|
||||||
window.addEventListener("resize", adjustHeight);
|
// Initial adjustment with delay to ensure player is fully rendered
|
||||||
|
const initialTimer = setTimeout(() => {
|
||||||
|
adjustHeight();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Set up resize listener
|
||||||
|
window.addEventListener('resize', adjustHeight);
|
||||||
|
|
||||||
|
// Create MutationObserver to monitor player changes
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
setTimeout(adjustHeight, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing both video container and controls
|
||||||
|
if (videoContainerRef.current) {
|
||||||
|
observer.observe(videoContainerRef.current, {
|
||||||
|
attributes: true,
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlsRef.current) {
|
||||||
|
observer.observe(controlsRef.current, {
|
||||||
|
attributes: true,
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up additional interval for continuous adjustments
|
||||||
|
const intervalId = setInterval(adjustHeight, 1000);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("resize", adjustHeight);
|
clearTimeout(initialTimer);
|
||||||
|
clearInterval(intervalId);
|
||||||
|
observer.disconnect();
|
||||||
|
window.removeEventListener('resize', adjustHeight);
|
||||||
};
|
};
|
||||||
});
|
}, [buffering, activeServerType, activeServerName, episodeId, streamUrl, episodes]);
|
||||||
|
|
||||||
function Tag({ bgColor, index, icon, text }) {
|
function Tag({ bgColor, index, icon, text }) {
|
||||||
return (
|
return (
|
||||||
@@ -182,13 +226,13 @@ export default function Watch() {
|
|||||||
}, [animeId, animeInfo]);
|
}, [animeId, animeInfo]);
|
||||||
return (
|
return (
|
||||||
<div className="w-full min-h-screen bg-[#0a0a0a]">
|
<div className="w-full min-h-screen bg-[#0a0a0a]">
|
||||||
<div className="w-full max-w-[1920px] mx-auto px-6 pt-[128px] pb-12 max-[1200px]:pt-[64px] max-[1024px]:px-4 max-md:pt-[50px]">
|
<div className="w-full max-w-[1920px] mx-auto px-6 pt-[80px] pb-12 max-[1200px]:pt-[48px] max-[1024px]:px-4 max-md:pt-[32px]">
|
||||||
<div className="grid grid-cols-[minmax(0,70%),minmax(0,30%)] gap-6 w-full h-full max-[1200px]:flex max-[1200px]:flex-col">
|
<div className="grid grid-cols-[minmax(0,70%),minmax(0,30%)] gap-6 w-full h-full max-[1200px]:flex max-[1200px]:flex-col">
|
||||||
{/* Left Column - Player, Controls, Servers */}
|
{/* Left Column - Player, Controls, Servers */}
|
||||||
<div className="flex flex-col w-full gap-6">
|
<div className="flex flex-col w-full gap-6">
|
||||||
<div className="player w-full h-fit bg-black flex flex-col rounded-xl overflow-hidden">
|
<div ref={playerRef} className="player w-full h-fit bg-black flex flex-col rounded-xl overflow-hidden">
|
||||||
{/* Video Container */}
|
{/* Video Container */}
|
||||||
<div className="w-full relative aspect-video bg-black">
|
<div ref={videoContainerRef} className="w-full relative aspect-video bg-black">
|
||||||
{!buffering ? (["hd-1", "hd-4"].includes(activeServerName.toLowerCase()) ?
|
{!buffering ? (["hd-1", "hd-4"].includes(activeServerName.toLowerCase()) ?
|
||||||
<IframePlayer
|
<IframePlayer
|
||||||
episodeId={episodeId}
|
episodeId={episodeId}
|
||||||
@@ -241,9 +285,9 @@ export default function Watch() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Controls Section */}
|
{/* Controls Section */}
|
||||||
<div className="bg-[#0f0f0f]">
|
<div className="bg-[#121212]">
|
||||||
{!buffering && (
|
{!buffering && (
|
||||||
<div className="border-b border-[#272727]">
|
<div ref={controlsRef}>
|
||||||
<Watchcontrols
|
<Watchcontrols
|
||||||
autoPlay={autoPlay}
|
autoPlay={autoPlay}
|
||||||
setAutoPlay={setAutoPlay}
|
setAutoPlay={setAutoPlay}
|
||||||
@@ -260,8 +304,7 @@ export default function Watch() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Title and Server Selection */}
|
{/* Title and Server Selection */}
|
||||||
<div className="p-3">
|
<div className="px-3 py-2">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Servers
|
<Servers
|
||||||
servers={servers}
|
servers={servers}
|
||||||
@@ -326,9 +369,20 @@ export default function Watch() {
|
|||||||
)}
|
)}
|
||||||
<div className="flex flex-col gap-y-4 flex-1">
|
<div className="flex flex-col gap-y-4 flex-1">
|
||||||
{animeInfo && animeInfo?.title ? (
|
{animeInfo && animeInfo?.title ? (
|
||||||
<h1 className="text-[28px] font-medium text-white leading-tight">
|
<Link
|
||||||
{language ? animeInfo?.title : animeInfo?.japanese_title}
|
to={`/${animeId}`}
|
||||||
</h1>
|
className="group"
|
||||||
|
>
|
||||||
|
<h1 className="text-[28px] font-medium text-white leading-tight group-hover:text-gray-300 transition-colors">
|
||||||
|
{language ? animeInfo?.title : animeInfo?.japanese_title}
|
||||||
|
</h1>
|
||||||
|
<div className="flex items-center gap-1.5 mt-1 text-gray-400 text-sm group-hover:text-white transition-colors">
|
||||||
|
<span>View Details</span>
|
||||||
|
<svg className="w-4 h-4 transform group-hover:translate-x-0.5 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton className="w-[170px] h-[20px] rounded-xl" />
|
<Skeleton className="w-[170px] h-[20px] rounded-xl" />
|
||||||
)}
|
)}
|
||||||
@@ -366,12 +420,6 @@ export default function Watch() {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<Link
|
|
||||||
to={`/${animeId}`}
|
|
||||||
className="w-fit px-4 py-2 bg-[#2a2a2a] hover:bg-[#333] text-gray-300 hover:text-white rounded-md transition-all text-sm font-medium mt-2"
|
|
||||||
>
|
|
||||||
View Details
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -380,22 +428,47 @@ export default function Watch() {
|
|||||||
{seasons?.length > 0 && (
|
{seasons?.length > 0 && (
|
||||||
<div className="p-6 bg-[#141414] rounded-lg">
|
<div className="p-6 bg-[#141414] rounded-lg">
|
||||||
<h2 className="text-xl font-semibold mb-4 text-white">More Seasons</h2>
|
<h2 className="text-xl font-semibold mb-4 text-white">More Seasons</h2>
|
||||||
<div className="grid grid-cols-5 gap-4 max-[1200px]:grid-cols-4 max-[900px]:grid-cols-3 max-[600px]:grid-cols-2">
|
<div className="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 sm:gap-4">
|
||||||
{seasons.map((season, index) => (
|
{seasons.map((season, index) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/${season.id}`}
|
to={`/${season.id}`}
|
||||||
key={index}
|
key={index}
|
||||||
className={`relative aspect-[2/3] rounded-lg overflow-hidden group ${
|
className={`relative w-full aspect-[3/1] rounded-lg overflow-hidden cursor-pointer group ${
|
||||||
animeId === String(season.id) ? "ring-2 ring-white" : ""
|
animeId === String(season.id)
|
||||||
|
? "ring-2 ring-white/40 shadow-lg shadow-white/10"
|
||||||
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${season.season_poster}`}
|
src={season.season_poster}
|
||||||
alt=""
|
alt={season.season}
|
||||||
className="w-full h-full object-cover transition-transform group-hover:scale-110"
|
className={`w-full h-full object-cover scale-150 ${
|
||||||
|
animeId === String(season.id)
|
||||||
|
? "opacity-50"
|
||||||
|
: "opacity-40 group-hover:opacity-50 transition-opacity"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-black bg-opacity-60 flex items-center justify-center p-3 group-hover:bg-opacity-40 transition-all">
|
{/* Dots Pattern Overlay */}
|
||||||
<p className="text-center text-sm font-medium">
|
<div
|
||||||
|
className="absolute inset-0 z-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('data:image/svg+xml,<svg width="3" height="3" viewBox="0 0 3 3" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="1.5" cy="1.5" r="0.5" fill="white" fill-opacity="0.25"/></svg>')`,
|
||||||
|
backgroundSize: '3px 3px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Dark Gradient Overlay */}
|
||||||
|
<div className={`absolute inset-0 z-20 bg-gradient-to-r ${
|
||||||
|
animeId === String(season.id)
|
||||||
|
? "from-black/50 to-transparent"
|
||||||
|
: "from-black/40 to-transparent"
|
||||||
|
}`} />
|
||||||
|
{/* Title Container */}
|
||||||
|
<div className="absolute inset-0 z-30 flex items-center justify-center">
|
||||||
|
<p className={`text-[14px] sm:text-[16px] font-bold text-center px-2 sm:px-4 transition-colors duration-300 ${
|
||||||
|
animeId === String(season.id)
|
||||||
|
? "text-white"
|
||||||
|
: "text-white/90 group-hover:text-white"
|
||||||
|
}`}>
|
||||||
{season.season}
|
{season.season}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -404,22 +477,29 @@ export default function Watch() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Recommended Section */}
|
{/* Right Column - Episodes and Related */}
|
||||||
{animeInfo?.recommended_data.length > 0 && (
|
<div className="flex flex-col gap-6 h-full">
|
||||||
<div className="p-6 bg-[#141414] rounded-lg">
|
{/* Episodes Section */}
|
||||||
<h2 className="text-xl font-semibold mb-4 text-white">Recommended</h2>
|
<div ref={episodesRef} className="episodes flex-shrink-0 bg-[#141414] rounded-lg overflow-hidden">
|
||||||
<CategoryCard
|
{!episodes ? (
|
||||||
data={animeInfo?.recommended_data}
|
<div className="h-full flex items-center justify-center">
|
||||||
limit={animeInfo?.recommended_data.length}
|
<BouncingLoader />
|
||||||
showViewMore={false}
|
</div>
|
||||||
|
) : (
|
||||||
|
<Episodelist
|
||||||
|
episodes={episodes}
|
||||||
|
currentEpisode={episodeId}
|
||||||
|
onEpisodeClick={(id) => setEpisodeId(id)}
|
||||||
|
totalEpisodes={totalEpisodes}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
{/* Related Anime Section */}
|
{/* Related Anime Section */}
|
||||||
{animeInfo && animeInfo.related_data ? (
|
{animeInfo && animeInfo.related_data ? (
|
||||||
<div className="p-6 bg-[#141414] rounded-lg">
|
<div className="bg-[#141414] rounded-lg p-6">
|
||||||
<h2 className="text-xl font-semibold mb-4 text-white">Related Anime</h2>
|
<h2 className="text-xl font-semibold mb-4 text-white">Related Anime</h2>
|
||||||
<Sidecard
|
<Sidecard
|
||||||
data={animeInfo.related_data}
|
data={animeInfo.related_data}
|
||||||
@@ -432,22 +512,6 @@ export default function Watch() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Column - Episodes */}
|
|
||||||
<div className="episodes h-full bg-[#141414] rounded-lg overflow-hidden">
|
|
||||||
{!episodes ? (
|
|
||||||
<div className="h-full flex items-center justify-center">
|
|
||||||
<BouncingLoader />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Episodelist
|
|
||||||
episodes={episodes}
|
|
||||||
currentEpisode={episodeId}
|
|
||||||
onEpisodeClick={(id) => setEpisodeId(id)}
|
|
||||||
totalEpisodes={totalEpisodes}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user