mirror of
https://github.com/JustAnimeCore/JustAnime.git
synced 2026-04-17 22:01:45 +00:00
fixed, made it working
This commit is contained in:
105
src/components/DiscordPopup.jsx
Normal file
105
src/components/DiscordPopup.jsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { FaDiscord, FaTelegram } from "react-icons/fa";
|
||||
|
||||
const DiscordPopup = () => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [shouldRender, setShouldRender] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if the user has opted out of seeing the popup
|
||||
const isHidden = localStorage.getItem("hideDiscordPopup");
|
||||
if (isHidden) return;
|
||||
|
||||
// Set a timer for 2 minutes (120,000 ms)
|
||||
const timer = setTimeout(() => {
|
||||
setShouldRender(true);
|
||||
// Brief delay to trigger entrance animation
|
||||
setTimeout(() => setIsVisible(true), 10);
|
||||
}, 60000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsVisible(false);
|
||||
// Wait for animation to finish before removing from DOM
|
||||
setTimeout(() => setShouldRender(false), 300);
|
||||
};
|
||||
|
||||
const handleNeverShowAgain = () => {
|
||||
localStorage.setItem("hideDiscordPopup", "true");
|
||||
handleClose();
|
||||
};
|
||||
|
||||
if (!shouldRender) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 z-[9999] flex items-center justify-center p-4 transition-all duration-300 ease-in-out ${isVisible ? "opacity-100 backdrop-blur-md pointer-events-auto" : "opacity-0 backdrop-blur-none pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`bg-[#1a1a1a] border border-[#2a2a2a] rounded-2xl shadow-[0_0_50px_-12px_rgba(0,0,0,0.8)] overflow-hidden max-w-sm w-full transition-all duration-300 transform ${isVisible ? "scale-100 translate-y-0" : "scale-95 translate-y-4"
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-5 border-b border-[#2a2a2a] bg-[#1a1a1a]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2.5 bg-[#5865F2] text-white rounded-xl shadow-md">
|
||||
<FaDiscord className="text-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-white text-[15px] leading-tight">Join Our Community</span>
|
||||
<span className="text-[10px] text-gray-400 uppercase tracking-widest font-bold mt-0.5">Discord & Telegram</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="p-1.5 hover:bg-[#2a2a2a] rounded-full transition-colors text-gray-400 hover:text-white"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 bg-[#1a1a1a]">
|
||||
<p className="text-[13px] text-gray-400 leading-relaxed font-medium">
|
||||
Join our official channels for early updates, announcements, and to connect with other fans!
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex flex-col gap-3">
|
||||
<a
|
||||
href="https://discord.gg/your-invite-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-2 w-full bg-[#5865F2] hover:bg-[#4759d8] text-white py-2.5 px-4 rounded-xl font-bold transition-all transform active:scale-[0.97] shadow-lg"
|
||||
>
|
||||
<FaDiscord className="text-lg" />
|
||||
JOIN DISCORD
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://tinyurl.com/JustAnimeZone"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-2 w-full bg-[#26A5E4] hover:bg-[#2295ce] text-white py-2.5 px-4 rounded-xl font-bold transition-all transform active:scale-[0.97] shadow-lg"
|
||||
>
|
||||
<FaTelegram className="text-lg" />
|
||||
JOIN TELEGRAM
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleNeverShowAgain}
|
||||
className="mt-5 w-full text-[11px] text-gray-500 hover:text-white underline-offset-4 hover:underline transition-colors py-1 font-semibold tracking-wide"
|
||||
>
|
||||
NEVER SHOW AGAIN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscordPopup;
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useLanguage } from "@/src/context/LanguageContext";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
import "./Banner.css";
|
||||
|
||||
function Banner({ item, index }) {
|
||||
@@ -16,19 +17,19 @@ function Banner({ item, index }) {
|
||||
<section className="spotlight w-full h-full relative rounded-2xl overflow-hidden">
|
||||
<img
|
||||
src={`${item.poster}`}
|
||||
alt={item.title}
|
||||
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||
className="absolute inset-0 object-cover w-full h-full rounded-2xl"
|
||||
/>
|
||||
<div className="spotlight-overlay absolute inset-0 z-[1] rounded-2xl"></div>
|
||||
|
||||
|
||||
<div className="absolute flex flex-col left-0 bottom-[40px] w-[55%] p-4 z-[2] max-[1390px]:w-[45%] max-[1390px]:bottom-[40px] max-[1300px]:w-[600px] max-[1120px]:w-[60%] max-md:w-[90%] max-md:bottom-[20px] max-[300px]:w-full">
|
||||
<p className="text-[#ffbade] font-semibold text-[20px] w-fit max-[1300px]:text-[15px]">
|
||||
#{index + 1} Spotlight
|
||||
</p>
|
||||
<h3 className="text-white line-clamp-2 text-5xl font-bold mt-4 text-left max-[1390px]:text-[45px] max-[1300px]:text-3xl max-[1300px]:mt-3 max-md:text-2xl max-md:mt-1 max-[575px]:text-[22px] max-sm:leading-6 max-sm:w-[80%] max-[320px]:w-full">
|
||||
{language === "EN" ? item.title : item.japanese_title}
|
||||
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||
</h3>
|
||||
|
||||
|
||||
{/* Mobile Buttons */}
|
||||
<div className="hidden max-md:flex max-md:mt-3 max-md:gap-x-3 max-md:w-full">
|
||||
<Link
|
||||
|
||||
@@ -6,9 +6,10 @@ import {
|
||||
faPlay,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FaChevronRight } from "react-icons/fa";
|
||||
import "./CategoryCard.css";
|
||||
import { useLanguage } from "@/src/context/LanguageContext";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
import "./CategoryCard.css";
|
||||
|
||||
const CategoryCard = React.memo(
|
||||
({
|
||||
@@ -23,7 +24,7 @@ const CategoryCard = React.memo(
|
||||
}) => {
|
||||
const { language } = useLanguage();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
if (limit) {
|
||||
data = data.slice(0, limit);
|
||||
}
|
||||
@@ -55,7 +56,7 @@ const CategoryCard = React.memo(
|
||||
if (
|
||||
JSON.stringify(prev.firstRow) !== JSON.stringify(newItems.firstRow) ||
|
||||
JSON.stringify(prev.remainingItems) !==
|
||||
JSON.stringify(newItems.remainingItems)
|
||||
JSON.stringify(newItems.remainingItems)
|
||||
) {
|
||||
return newItems;
|
||||
}
|
||||
@@ -90,11 +91,10 @@ const CategoryCard = React.memo(
|
||||
<>
|
||||
{categoryPage && (
|
||||
<div
|
||||
className={`grid grid-cols-4 gap-x-3 gap-y-8 transition-all duration-300 ease-in-out ${
|
||||
categoryPage && itemsToRender.firstRow.length > 0
|
||||
? "mt-8 max-[758px]:hidden"
|
||||
: ""
|
||||
}`}
|
||||
className={`grid grid-cols-4 gap-x-3 gap-y-8 transition-all duration-300 ease-in-out ${categoryPage && itemsToRender.firstRow.length > 0
|
||||
? "mt-8 max-[758px]:hidden"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{itemsToRender.firstRow.map((item, index) => (
|
||||
<div
|
||||
@@ -107,17 +107,16 @@ const CategoryCard = React.memo(
|
||||
className="inline-block bg-gray-900 absolute left-0 top-0 w-full h-full group hover:cursor-pointer"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`${
|
||||
path === "top-upcoming"
|
||||
? `/${item.id}`
|
||||
: `/watch/${item.id}`
|
||||
`${path === "top-upcoming"
|
||||
? `/${item.id}`
|
||||
: `/watch/${item.id}`
|
||||
}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={`${item.poster}`}
|
||||
alt={item.title}
|
||||
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||
className="block w-full h-full object-cover transition-all duration-500 ease-in-out group-hover:scale-105 group-hover:blur-sm"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-center justify-center">
|
||||
@@ -131,10 +130,10 @@ const CategoryCard = React.memo(
|
||||
</div>
|
||||
{(item.tvInfo?.rating === "18+" ||
|
||||
item?.adultContent === true) && (
|
||||
<div className="text-white px-2 py-0.5 rounded-lg bg-red-600 absolute top-3 left-3 flex items-center justify-center text-[12px] font-bold">
|
||||
18+
|
||||
</div>
|
||||
)}
|
||||
<div className="text-white px-2 py-0.5 rounded-lg bg-red-600 absolute top-3 left-3 flex items-center justify-center text-[12px] font-bold">
|
||||
18+
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-3 pb-2 bg-gradient-to-t from-black/80 via-black/50 to-transparent">
|
||||
<div className="flex items-center justify-start w-full space-x-1.5 z-[100] flex-wrap gap-y-1.5">
|
||||
{item.tvInfo?.sub && (
|
||||
@@ -177,9 +176,9 @@ const CategoryCard = React.memo(
|
||||
{(item.tvInfo?.duration || item.duration) && (
|
||||
<div className="bg-[#2a2a2a] text-white rounded-[2px] px-2 py-1 text-[11px] font-medium">
|
||||
{item.tvInfo?.duration === "m" ||
|
||||
item.tvInfo?.duration === "?" ||
|
||||
item.duration === "m" ||
|
||||
item.duration === "?"
|
||||
item.tvInfo?.duration === "?" ||
|
||||
item.duration === "m" ||
|
||||
item.duration === "?"
|
||||
? "N/A"
|
||||
: item.tvInfo?.duration || item.duration || "N/A"}
|
||||
</div>
|
||||
@@ -191,7 +190,7 @@ const CategoryCard = React.memo(
|
||||
to={`/${item.id}`}
|
||||
className="text-white font-semibold mt-3 item-title hover:text-white hover:cursor-pointer line-clamp-1"
|
||||
>
|
||||
{language === "EN" ? item.title : item.japanese_title}
|
||||
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||
</Link>
|
||||
{item.description && (
|
||||
<div className="line-clamp-3 text-[13px] font-light text-gray-400 mt-3 max-[1200px]:hidden">
|
||||
@@ -214,17 +213,16 @@ const CategoryCard = React.memo(
|
||||
className="inline-block bg-gray-900 absolute left-0 top-0 w-full h-full group hover:cursor-pointer"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`${
|
||||
path === "top-upcoming"
|
||||
? `/${item.id}`
|
||||
: `/watch/${item.id}`
|
||||
`${path === "top-upcoming"
|
||||
? `/${item.id}`
|
||||
: `/watch/${item.id}`
|
||||
}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={`${item.poster}`}
|
||||
alt={item.title}
|
||||
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||
className="block w-full h-full object-cover transition-all duration-500 ease-in-out group-hover:scale-105 group-hover:blur-sm"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-center justify-center">
|
||||
@@ -238,10 +236,10 @@ const CategoryCard = React.memo(
|
||||
</div>
|
||||
{(item.tvInfo?.rating === "18+" ||
|
||||
item?.adultContent === true) && (
|
||||
<div className="text-white px-2 py-0.5 rounded-lg bg-red-600 absolute top-3 left-3 flex items-center justify-center text-[12px] font-bold">
|
||||
18+
|
||||
</div>
|
||||
)}
|
||||
<div className="text-white px-2 py-0.5 rounded-lg bg-red-600 absolute top-3 left-3 flex items-center justify-center text-[12px] font-bold">
|
||||
18+
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/80 via-black/50 to-transparent">
|
||||
<div className="flex items-center justify-start w-full space-x-1 max-[478px]:space-x-0.5 z-[100] flex-wrap gap-y-1">
|
||||
{item.tvInfo?.sub && (
|
||||
@@ -284,9 +282,9 @@ const CategoryCard = React.memo(
|
||||
{(item.tvInfo?.duration || item.duration) && (
|
||||
<div className="bg-[#2a2a2a] text-white rounded-[2px] px-1.5 py-0.5 text-[10px] font-medium max-[478px]:py-0.5 max-[478px]:px-1 max-[478px]:hidden">
|
||||
{item.tvInfo?.duration === "m" ||
|
||||
item.tvInfo?.duration === "?" ||
|
||||
item.duration === "m" ||
|
||||
item.duration === "?"
|
||||
item.tvInfo?.duration === "?" ||
|
||||
item.duration === "m" ||
|
||||
item.duration === "?"
|
||||
? "N/A"
|
||||
: item.tvInfo?.duration || item.duration || "N/A"}
|
||||
</div>
|
||||
@@ -298,7 +296,7 @@ const CategoryCard = React.memo(
|
||||
to={`/${item.id}`}
|
||||
className="text-white font-semibold mt-3 item-title hover:text-white hover:cursor-pointer line-clamp-1"
|
||||
>
|
||||
{language === "EN" ? item.title : item.japanese_title}
|
||||
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { FaHistory, FaChevronLeft, FaChevronRight } from "react-icons/fa";
|
||||
import { useLanguage } from "@/src/context/LanguageContext";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
|
||||
const ContinueWatching = () => {
|
||||
const [watchList, setWatchList] = useState([]);
|
||||
@@ -92,9 +93,9 @@ const ContinueWatching = () => {
|
||||
>
|
||||
<img
|
||||
src={`${item?.poster}`}
|
||||
alt={item?.title}
|
||||
alt={getSafeTitle(item?.title, language, item?.japanese_title)}
|
||||
className="block w-full h-full object-cover transition-all duration-500 ease-in-out group-hover:scale-105 group-hover:blur-sm"
|
||||
title={item?.title}
|
||||
title={getSafeTitle(item?.title, language, item?.japanese_title)}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-center justify-center">
|
||||
@@ -113,9 +114,7 @@ const ContinueWatching = () => {
|
||||
)}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-3 pb-2 bg-gradient-to-t from-black/90 via-black/60 to-transparent">
|
||||
<p className="text-white text-[15px] font-bold text-left truncate mb-1.5 max-[450px]:text-sm drop-shadow-lg">
|
||||
{language === "EN"
|
||||
? item?.title
|
||||
: item?.japanese_title}
|
||||
{getSafeTitle(item?.title, language, item?.japanese_title)}
|
||||
</p>
|
||||
<p className="text-gray-200 text-[13px] font-semibold text-left max-[450px]:text-[12px] drop-shadow-md">
|
||||
Episode {item.episodeNum}
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
import logoTitle from "@/src/config/logoTitle.js";
|
||||
import website_name from "@/src/config/website.js";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FaDiscord, FaTelegram } from "react-icons/fa";
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<footer className="w-full mt-16">
|
||||
{/* Logo Section */}
|
||||
<div className="max-w-[1920px] mx-auto px-4">
|
||||
<div className="flex justify-center sm:justify-start items-center gap-6">
|
||||
<img
|
||||
src="/footer.png"
|
||||
alt={logoTitle}
|
||||
className="h-[100px] w-[200px] object-contain"
|
||||
/>
|
||||
<div className="flex flex-col sm:flex-row justify-between items-center gap-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<img
|
||||
src="/footer.png"
|
||||
alt={logoTitle}
|
||||
className="h-[100px] w-[200px] object-contain"
|
||||
/>
|
||||
<div className="flex items-center gap-4 border-l border-white/10 pl-6 h-10">
|
||||
<a
|
||||
href="https://discord.gg/your-invite-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-white/40 hover:text-[#5865F2] transition-all hover:scale-110"
|
||||
>
|
||||
<FaDiscord size={28} />
|
||||
</a>
|
||||
<a
|
||||
href="https://t.me/your-telegram-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-white/40 hover:text-[#26A5E4] transition-all hover:scale-110"
|
||||
>
|
||||
<FaTelegram size={28} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -59,11 +59,13 @@ export default function Player({
|
||||
animeInfo,
|
||||
episodeNum,
|
||||
streamInfo,
|
||||
serverName,
|
||||
}) {
|
||||
const artRef = useRef(null);
|
||||
const leftAtRef = useRef(0);
|
||||
const leftAtRef = useRef(0);
|
||||
const proxy = import.meta.env.VITE_PROXY_URL;
|
||||
const m3u8proxy = import.meta.env.VITE_M3U8_PROXY_URL?.split(",") || [];
|
||||
const m3u8proxyHD3 = import.meta.env.VITE_M3U8_PROXY_HD3;
|
||||
const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
|
||||
episodes?.findIndex(
|
||||
(episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
|
||||
@@ -117,11 +119,11 @@ export default function Player({
|
||||
const currentTime = Math.round(video.currentTime);
|
||||
const duration = Math.round(video.duration);
|
||||
if (duration > 0 && currentTime >= duration) {
|
||||
art.pause();
|
||||
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
||||
playNext(
|
||||
episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
|
||||
);
|
||||
art.pause();
|
||||
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
||||
playNext(
|
||||
episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -131,11 +133,11 @@ export default function Player({
|
||||
const currentTime = Math.round(video.currentTime);
|
||||
const duration = Math.round(video.duration);
|
||||
if (duration > 0 && currentTime >= duration) {
|
||||
art.pause();
|
||||
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
||||
playNext(
|
||||
episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
|
||||
);
|
||||
art.pause();
|
||||
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
||||
playNext(
|
||||
episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -211,18 +213,21 @@ export default function Player({
|
||||
if (!streamUrl || !artRef.current) return;
|
||||
const iframeUrl = streamInfo?.streamingLink?.iframe;
|
||||
const headers = {};
|
||||
headers.referer=new URL(iframeUrl).origin+"/";
|
||||
console.log(m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)] +
|
||||
encodeURIComponent(streamUrl) +
|
||||
"&headers=" +
|
||||
encodeURIComponent(JSON.stringify(headers)));
|
||||
headers.referer = new URL(iframeUrl).origin + "/";
|
||||
const finalProxy = (serverName === "hd-3" && m3u8proxyHD3)
|
||||
? m3u8proxyHD3
|
||||
: m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)];
|
||||
console.log(finalProxy +
|
||||
encodeURIComponent(streamUrl) +
|
||||
"&headers=" +
|
||||
encodeURIComponent(JSON.stringify(headers)));
|
||||
|
||||
const art = new Artplayer({
|
||||
url:
|
||||
m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)] +
|
||||
finalProxy +
|
||||
encodeURIComponent(streamUrl) +
|
||||
"&headers=" +
|
||||
encodeURIComponent(JSON.stringify(headers)),
|
||||
"&headers=" +
|
||||
encodeURIComponent(JSON.stringify(headers)),
|
||||
container: artRef.current,
|
||||
type: "m3u8",
|
||||
autoplay: autoPlay,
|
||||
|
||||
@@ -9,8 +9,11 @@ import {
|
||||
faMicrophone,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { Link } from "react-router-dom";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
import { useLanguage } from "@/src/context/LanguageContext";
|
||||
|
||||
function Qtip({ id }) {
|
||||
const { language } = useLanguage();
|
||||
const [qtip, setQtip] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -38,7 +41,7 @@ function Qtip({ id }) {
|
||||
) : (
|
||||
<div className="w-full flex flex-col justify-start gap-y-2">
|
||||
<h1 className="text-xl font-semibold text-white text-[13px] leading-6">
|
||||
{qtip.title}
|
||||
{getSafeTitle(qtip.title, language, qtip.japaneseTitle)}
|
||||
</h1>
|
||||
<div className="w-full flex items-center relative mt-2">
|
||||
{qtip?.rating && (
|
||||
|
||||
@@ -36,12 +36,14 @@ function Servers({
|
||||
setActiveServerId(matchingServer.data_id);
|
||||
setActiveServerType(matchingServer.type);
|
||||
} else if (servers && servers.length > 0) {
|
||||
setActiveServerId(servers[0].data_id);
|
||||
setActiveServerType(servers[0].type);
|
||||
const defaultServer = servers.find(s => s.serverName === "HD-2") || servers[0];
|
||||
setActiveServerId(defaultServer.data_id);
|
||||
setActiveServerType(defaultServer.type);
|
||||
}
|
||||
} else if (servers && servers.length > 0) {
|
||||
setActiveServerId(servers[0].data_id);
|
||||
setActiveServerType(servers[0].type);
|
||||
const defaultServer = servers.find(s => s.serverName === "HD-2") || servers[0];
|
||||
setActiveServerId(defaultServer.data_id);
|
||||
setActiveServerType(defaultServer.type);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [servers]);
|
||||
@@ -77,11 +79,10 @@ function Servers({
|
||||
</div>
|
||||
<div className="bg-[#1f1f1f] flex flex-col max-[600px]:rounded-lg max-[600px]:p-2">
|
||||
{rawServers.length > 0 && (
|
||||
<div className={`servers px-2 flex items-center flex-wrap gap-y-1 ml-2 max-[600px]:py-1.5 max-[600px]:px-1 max-[600px]:ml-0 ${
|
||||
dubServers.length === 0 || subServers.length === 0
|
||||
<div className={`servers px-2 flex items-center flex-wrap gap-y-1 ml-2 max-[600px]:py-1.5 max-[600px]:px-1 max-[600px]:ml-0 ${dubServers.length === 0 || subServers.length === 0
|
||||
? "h-1/2"
|
||||
: "h-full"
|
||||
}`}>
|
||||
}`}>
|
||||
<div className="flex items-center gap-x-2 min-w-[65px]">
|
||||
<FontAwesomeIcon
|
||||
icon={faFile}
|
||||
@@ -93,11 +94,10 @@ function Servers({
|
||||
{rawServers.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${
|
||||
activeServerId === item?.data_id
|
||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${activeServerId === item?.data_id
|
||||
? "bg-[#e0e0e0] text-black"
|
||||
: "bg-[#373737] text-white"
|
||||
} max-[700px]:px-3 max-[600px]:px-2 max-[600px]:py-1`}
|
||||
} max-[700px]:px-3 max-[600px]:px-2 max-[600px]:py-1`}
|
||||
onClick={() => handleServerSelect(item)}
|
||||
>
|
||||
<p className="text-[13px] font-semibold max-[600px]:text-[12px]">
|
||||
@@ -109,9 +109,8 @@ function Servers({
|
||||
</div>
|
||||
)}
|
||||
{subServers.length > 0 && (
|
||||
<div className={`servers px-2 flex items-center flex-wrap gap-y-1 ml-2 max-[600px]:py-1.5 max-[600px]:px-1 max-[600px]:ml-0 ${
|
||||
dubServers.length === 0 ? "h-1/2" : "h-full"
|
||||
}`}>
|
||||
<div className={`servers px-2 flex items-center flex-wrap gap-y-1 ml-2 max-[600px]:py-1.5 max-[600px]:px-1 max-[600px]:ml-0 ${dubServers.length === 0 ? "h-1/2" : "h-full"
|
||||
}`}>
|
||||
<div className="flex items-center gap-x-2 min-w-[65px]">
|
||||
<FontAwesomeIcon
|
||||
icon={faClosedCaptioning}
|
||||
@@ -123,11 +122,10 @@ function Servers({
|
||||
{subServers.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${
|
||||
activeServerId === item?.data_id
|
||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${activeServerId === item?.data_id
|
||||
? "bg-[#e0e0e0] text-black"
|
||||
: "bg-[#373737] text-white"
|
||||
} max-[700px]:px-3 max-[600px]:px-2 max-[600px]:py-1`}
|
||||
} max-[700px]:px-3 max-[600px]:px-2 max-[600px]:py-1`}
|
||||
onClick={() => handleServerSelect(item)}
|
||||
>
|
||||
<p className="text-[13px] font-semibold max-[600px]:text-[12px]">
|
||||
@@ -139,9 +137,8 @@ function Servers({
|
||||
</div>
|
||||
)}
|
||||
{dubServers.length > 0 && (
|
||||
<div className={`servers px-2 flex items-center flex-wrap gap-y-1 ml-2 max-[600px]:py-1.5 max-[600px]:px-1 max-[600px]:ml-0 ${
|
||||
subServers.length === 0 ? "h-1/2" : "h-full"
|
||||
}`}>
|
||||
<div className={`servers px-2 flex items-center flex-wrap gap-y-1 ml-2 max-[600px]:py-1.5 max-[600px]:px-1 max-[600px]:ml-0 ${subServers.length === 0 ? "h-1/2" : "h-full"
|
||||
}`}>
|
||||
<div className="flex items-center gap-x-2 min-w-[65px]">
|
||||
<FontAwesomeIcon
|
||||
icon={faMicrophone}
|
||||
@@ -153,11 +150,10 @@ function Servers({
|
||||
{dubServers.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${
|
||||
activeServerId === item?.data_id
|
||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${activeServerId === item?.data_id
|
||||
? "bg-[#e0e0e0] text-black"
|
||||
: "bg-[#373737] text-white"
|
||||
} max-[700px]:px-3 max-[600px]:px-2 max-[600px]:py-1`}
|
||||
} max-[700px]:px-3 max-[600px]:px-2 max-[600px]:py-1`}
|
||||
onClick={() => handleServerSelect(item)}
|
||||
>
|
||||
<p className="text-[13px] font-semibold max-[600px]:text-[12px]">
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useLanguage } from "@/src/context/LanguageContext";
|
||||
import { Link } from "react-router-dom";
|
||||
import useToolTipPosition from "@/src/hooks/useToolTipPosition";
|
||||
import Qtip from "../qtip/Qtip";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
|
||||
function Sidecard({ data, label, className }) {
|
||||
const { language } = useLanguage();
|
||||
@@ -50,27 +51,25 @@ function Sidecard({ data, label, className }) {
|
||||
<div className="flex items-start gap-3 p-2 rounded-lg transition-colors hover:bg-[#1f1f1f]">
|
||||
{hoveredItem === item.id + index && window.innerWidth > 1024 && (
|
||||
<div
|
||||
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition} ${
|
||||
tooltipPosition === "top-1/2"
|
||||
? "translate-y-[50px]"
|
||||
: "translate-y-[-50px]"
|
||||
} z-[100000] transform transition-all duration-300 ease-in-out ${
|
||||
hoveredItem === item.id + index
|
||||
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition} ${tooltipPosition === "top-1/2"
|
||||
? "translate-y-[50px]"
|
||||
: "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
|
||||
src={`${item.poster}`}
|
||||
alt={item.title}
|
||||
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||
className="w-[50px] h-[70px] rounded object-cover"
|
||||
/>
|
||||
<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-1">
|
||||
{language === "EN" ? item.title : item.japanese_title}
|
||||
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||
</span>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{item.tvInfo?.sub && (
|
||||
|
||||
@@ -3,8 +3,11 @@ import { useEffect, useState } from "react";
|
||||
import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
|
||||
import { FaChevronRight } from "react-icons/fa";
|
||||
import { Link } from "react-router-dom";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
import { useLanguage } from "@/src/context/LanguageContext";
|
||||
|
||||
function Suggestion({ keyword, className, onSuggestionClick }) {
|
||||
const { language } = useLanguage();
|
||||
const [suggestion, setSuggestion] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -31,10 +34,9 @@ function Suggestion({ keyword, className, onSuggestionClick }) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-zinc-900 ${className} flex ${
|
||||
loading ? "justify-center py-4" : "justify-start"
|
||||
} ${!suggestion ? "p-2" : "justify-start"} items-center rounded-lg`}
|
||||
style={{
|
||||
className={`bg-zinc-900 ${className} flex ${loading ? "justify-center py-4" : "justify-start"
|
||||
} ${!suggestion ? "p-2" : "justify-start"} items-center rounded-lg`}
|
||||
style={{
|
||||
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
|
||||
border: "1px solid rgba(255, 255, 255, 0.05)"
|
||||
}}
|
||||
@@ -61,20 +63,15 @@ function Suggestion({ keyword, className, onSuggestionClick }) {
|
||||
<img
|
||||
src={`${item.poster}`}
|
||||
className="w-[45px] h-[65px] flex-shrink-0 object-cover rounded-md shadow-lg"
|
||||
alt=""
|
||||
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||
onError={(e) => {
|
||||
e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col gap-y-[2px]">
|
||||
{item?.title && (
|
||||
{(item?.title || item?.japanese_title) && (
|
||||
<h1 className="line-clamp-1 leading-5 font-semibold text-[14px] text-gray-100 group-hover:text-white">
|
||||
{item.title || "N/A"}
|
||||
</h1>
|
||||
)}
|
||||
{item?.japanese_title && (
|
||||
<h1 className="line-clamp-1 leading-4 text-[12px] font-normal text-gray-400">
|
||||
{item.japanese_title || "N/A"}
|
||||
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||
</h1>
|
||||
)}
|
||||
{(item?.releaseDate || item?.showType || item?.duration) && (
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useLanguage } from "@/src/context/LanguageContext";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import useToolTipPosition from "@/src/hooks/useToolTipPosition";
|
||||
import Qtip from "../qtip/Qtip";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
|
||||
function Topten({ data, className }) {
|
||||
const { language } = useLanguage();
|
||||
@@ -29,8 +30,8 @@ function Topten({ data, className }) {
|
||||
activePeriod === "today"
|
||||
? data.today
|
||||
: activePeriod === "week"
|
||||
? data.week
|
||||
: data.month;
|
||||
? data.week
|
||||
: data.month;
|
||||
|
||||
const { tooltipPosition, tooltipHorizontalPosition, cardRefs } =
|
||||
useToolTipPosition(hoveredItem, currentData);
|
||||
@@ -56,11 +57,10 @@ function Topten({ data, className }) {
|
||||
{["today", "week", "month"].map((period) => (
|
||||
<li
|
||||
key={period}
|
||||
className={`cursor-pointer p-1.5 px-4 transition-all duration-200 ${
|
||||
activePeriod === period
|
||||
? "bg-white text-black font-medium"
|
||||
: "text-gray-400 hover:text-white hover:bg-[#2a2a2a]"
|
||||
}`}
|
||||
className={`cursor-pointer p-1.5 px-4 transition-all duration-200 ${activePeriod === period
|
||||
? "bg-white text-black font-medium"
|
||||
: "text-gray-400 hover:text-white hover:bg-[#2a2a2a]"
|
||||
}`}
|
||||
onClick={() => handlePeriodChange(period)}
|
||||
>
|
||||
{period.charAt(0).toUpperCase() + period.slice(1)}
|
||||
@@ -78,11 +78,10 @@ function Topten({ data, className }) {
|
||||
ref={(el) => (cardRefs.current[index] = el)}
|
||||
>
|
||||
<h1
|
||||
className={`font-bold text-2xl transition-colors ${
|
||||
index < 3
|
||||
? "text-white border-b-2 border-white pb-0.5"
|
||||
: "text-gray-600"
|
||||
} max-[350px]:hidden`}
|
||||
className={`font-bold text-2xl transition-colors ${index < 3
|
||||
? "text-white border-b-2 border-white pb-0.5"
|
||||
: "text-gray-600"
|
||||
} max-[350px]:hidden`}
|
||||
>
|
||||
{`${index + 1 < 10 ? "0" : ""}${index + 1}`}
|
||||
</h1>
|
||||
@@ -97,7 +96,7 @@ function Topten({ data, className }) {
|
||||
>
|
||||
<img
|
||||
src={`${item.poster}`}
|
||||
alt={item.title}
|
||||
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||
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}`)}
|
||||
onMouseEnter={() => handleMouseEnter(item, index)}
|
||||
@@ -109,17 +108,15 @@ function Topten({ data, className }) {
|
||||
window.innerWidth > 1024 && (
|
||||
<div
|
||||
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition}
|
||||
${
|
||||
tooltipPosition === "top-1/2"
|
||||
${tooltipPosition === "top-1/2"
|
||||
? "translate-y-[50px]"
|
||||
: "translate-y-[-50px]"
|
||||
}
|
||||
}
|
||||
z-[100000] transform transition-all duration-300 ease-in-out
|
||||
${
|
||||
hoveredItem === item.id + index
|
||||
${hoveredItem === item.id + index
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 translate-y-2"
|
||||
}`}
|
||||
}`}
|
||||
onMouseEnter={() => {
|
||||
if (hoverTimeout) clearTimeout(hoverTimeout);
|
||||
}}
|
||||
@@ -135,7 +132,7 @@ function Topten({ data, className }) {
|
||||
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)}
|
||||
>
|
||||
{language === "EN" ? item.title : item.japanese_title}
|
||||
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||
</Link>
|
||||
<div className="flex flex-wrap items-center w-fit space-x-2 max-[350px]:gap-y-[3px]">
|
||||
{item.tvInfo?.sub && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
faMicrophone,
|
||||
faFire
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||
|
||||
const Trending = ({ trending, className }) => {
|
||||
const { language } = useLanguage();
|
||||
@@ -29,7 +30,7 @@ const Trending = ({ trending, className }) => {
|
||||
<div className="relative">
|
||||
<img
|
||||
src={item.poster}
|
||||
alt={item.title}
|
||||
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||
className="w-[50px] h-[70px] rounded object-cover"
|
||||
/>
|
||||
<div className="absolute top-0 left-0 bg-white/90 text-black text-xs font-bold px-1.5 rounded-br">
|
||||
@@ -38,7 +39,7 @@ const Trending = ({ trending, className }) => {
|
||||
</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}
|
||||
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||
</span>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{item.tvInfo?.sub && (
|
||||
|
||||
Reference in New Issue
Block a user