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:
65
index.html
65
index.html
@@ -1,11 +1,23 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<script src="https://pl27909393.effectivegatecpm.com/c3/0b/f8/c30bf8d3c1ffabac1cad25285ad62c97.js"></script>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
|
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<!-- Mobile & PWA Meta Tags -->
|
||||||
|
<meta name="theme-color" content="#1a1a2e" />
|
||||||
|
<meta name="msapplication-navbutton-color" content="#1a1a2e" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="JustAnime" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#1a1a2e" />
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="JustAnime is a Free anime streaming website which you can watch English Subbed and Dubbed Anime online. WATCH NOW!. Hianime, 9animetv, aniwatchtv"
|
content="JustAnime is a Free anime streaming website which you can watch English Subbed and Dubbed Anime online. WATCH NOW!. Hianime, 9animetv, aniwatchtv"
|
||||||
@@ -47,44 +59,47 @@
|
|||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "VideoObject",
|
"@type": "WebSite",
|
||||||
"name": "JustAnime - Watch Anime Free",
|
"name": "JustAnime",
|
||||||
|
"alternateName": ["JustAnime.to", "Just Anime"],
|
||||||
|
"url": "https://justanime.to",
|
||||||
"description": "JustAnime offers free streaming of English-subbed and dubbed anime series and movies. No account needed and no ads!",
|
"description": "JustAnime offers free streaming of English-subbed and dubbed anime series and movies. No account needed and no ads!",
|
||||||
"thumbnailUrl": "https://github.com/tejaspanchall/JustAnime/blob/main/public/home.PNG",
|
"potentialAction": {
|
||||||
"uploadDate": "2024-11-08",
|
"@type": "SearchAction",
|
||||||
"contentUrl": "https://justanime.to",
|
"target": {
|
||||||
"duration": "PT30M",
|
"@type": "EntryPoint",
|
||||||
"interactionStatistic": {
|
"urlTemplate": "https://justanime.to/search?keyword={search_term_string}"
|
||||||
"@type": "InteractionCounter",
|
|
||||||
"interactionType": { "@type": "WatchAction" },
|
|
||||||
"userInteractionCount": 50000
|
|
||||||
},
|
},
|
||||||
"author": {
|
"query-input": "required name=search_term_string"
|
||||||
"@type": "Individual",
|
|
||||||
"name": "itzzzme",
|
|
||||||
"url": "https://justanime.to"
|
|
||||||
},
|
},
|
||||||
"publisher": {
|
"publisher": {
|
||||||
"@type": "Individual",
|
"@type": "Organization",
|
||||||
"name": "itzzzme",
|
"name": "JustAnime",
|
||||||
|
"url": "https://justanime.to",
|
||||||
"logo": {
|
"logo": {
|
||||||
"@type": "ImageObject",
|
"@type": "ImageObject",
|
||||||
"url": "https://github.com/tejaspanchall/JustAnime/blob/main/public/logo.png"
|
"url": "https://justanime.to/logo.png",
|
||||||
}
|
"width": 512,
|
||||||
|
"height": 512
|
||||||
},
|
},
|
||||||
"potentialAction": {
|
"contactPoint": {
|
||||||
"@type": "WatchAction",
|
"@type": "ContactPoint",
|
||||||
"target": "https://justanime.to"
|
"email": "justanimexyz@gmail.com",
|
||||||
|
"contactType": "customer service"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<link rel="canonical" href="https://justanime.to" />
|
<link rel="canonical" href="https://justanime.to" />
|
||||||
|
|
||||||
<link
|
<!-- Preload Critical Resources -->
|
||||||
rel="stylesheet"
|
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" /></noscript>
|
||||||
/>
|
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||||
|
<link rel="dns-prefetch" href="//cdnjs.cloudflare.com" />
|
||||||
|
<link rel="dns-prefetch" href="//www.googletagmanager.com" />
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SplashScreen from "./components/splashscreen/SplashScreen";
|
|||||||
import Terms from "./pages/terms/Terms";
|
import Terms from "./pages/terms/Terms";
|
||||||
import DMCA from "./pages/dmca/DMCA";
|
import DMCA from "./pages/dmca/DMCA";
|
||||||
import Contact from "./pages/contact/Contact";
|
import Contact from "./pages/contact/Contact";
|
||||||
|
import DiscordPopup from "./components/DiscordPopup";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -75,6 +76,7 @@ function App() {
|
|||||||
</main>
|
</main>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<SpeedInsights />
|
<SpeedInsights />
|
||||||
|
<DiscordPopup />
|
||||||
</div>
|
</div>
|
||||||
</HomeInfoProvider>
|
</HomeInfoProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
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";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useLanguage } from "@/src/context/LanguageContext";
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
import "./Banner.css";
|
import "./Banner.css";
|
||||||
|
|
||||||
function Banner({ item, index }) {
|
function Banner({ item, index }) {
|
||||||
@@ -16,7 +17,7 @@ function Banner({ item, index }) {
|
|||||||
<section className="spotlight w-full h-full relative rounded-2xl overflow-hidden">
|
<section className="spotlight w-full h-full relative rounded-2xl overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
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"
|
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="spotlight-overlay absolute inset-0 z-[1] rounded-2xl"></div>
|
||||||
@@ -26,7 +27,7 @@ function Banner({ item, index }) {
|
|||||||
#{index + 1} Spotlight
|
#{index + 1} Spotlight
|
||||||
</p>
|
</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">
|
<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>
|
</h3>
|
||||||
|
|
||||||
{/* Mobile Buttons */}
|
{/* Mobile Buttons */}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import {
|
|||||||
faPlay,
|
faPlay,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FaChevronRight } from "react-icons/fa";
|
import { FaChevronRight } from "react-icons/fa";
|
||||||
import "./CategoryCard.css";
|
|
||||||
import { useLanguage } from "@/src/context/LanguageContext";
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
import "./CategoryCard.css";
|
||||||
|
|
||||||
const CategoryCard = React.memo(
|
const CategoryCard = React.memo(
|
||||||
({
|
({
|
||||||
@@ -90,8 +91,7 @@ const CategoryCard = React.memo(
|
|||||||
<>
|
<>
|
||||||
{categoryPage && (
|
{categoryPage && (
|
||||||
<div
|
<div
|
||||||
className={`grid grid-cols-4 gap-x-3 gap-y-8 transition-all duration-300 ease-in-out ${
|
className={`grid grid-cols-4 gap-x-3 gap-y-8 transition-all duration-300 ease-in-out ${categoryPage && itemsToRender.firstRow.length > 0
|
||||||
categoryPage && itemsToRender.firstRow.length > 0
|
|
||||||
? "mt-8 max-[758px]:hidden"
|
? "mt-8 max-[758px]:hidden"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
@@ -107,8 +107,7 @@ const CategoryCard = React.memo(
|
|||||||
className="inline-block bg-gray-900 absolute left-0 top-0 w-full h-full group hover:cursor-pointer"
|
className="inline-block bg-gray-900 absolute left-0 top-0 w-full h-full group hover:cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(
|
navigate(
|
||||||
`${
|
`${path === "top-upcoming"
|
||||||
path === "top-upcoming"
|
|
||||||
? `/${item.id}`
|
? `/${item.id}`
|
||||||
: `/watch/${item.id}`
|
: `/watch/${item.id}`
|
||||||
}`
|
}`
|
||||||
@@ -117,7 +116,7 @@ const CategoryCard = React.memo(
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
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"
|
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">
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-center justify-center">
|
||||||
@@ -191,7 +190,7 @@ const CategoryCard = React.memo(
|
|||||||
to={`/${item.id}`}
|
to={`/${item.id}`}
|
||||||
className="text-white font-semibold mt-3 item-title hover:text-white hover:cursor-pointer line-clamp-1"
|
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>
|
</Link>
|
||||||
{item.description && (
|
{item.description && (
|
||||||
<div className="line-clamp-3 text-[13px] font-light text-gray-400 mt-3 max-[1200px]:hidden">
|
<div className="line-clamp-3 text-[13px] font-light text-gray-400 mt-3 max-[1200px]:hidden">
|
||||||
@@ -214,8 +213,7 @@ const CategoryCard = React.memo(
|
|||||||
className="inline-block bg-gray-900 absolute left-0 top-0 w-full h-full group hover:cursor-pointer"
|
className="inline-block bg-gray-900 absolute left-0 top-0 w-full h-full group hover:cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(
|
navigate(
|
||||||
`${
|
`${path === "top-upcoming"
|
||||||
path === "top-upcoming"
|
|
||||||
? `/${item.id}`
|
? `/${item.id}`
|
||||||
: `/watch/${item.id}`
|
: `/watch/${item.id}`
|
||||||
}`
|
}`
|
||||||
@@ -224,7 +222,7 @@ const CategoryCard = React.memo(
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
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"
|
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">
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-center justify-center">
|
||||||
@@ -298,7 +296,7 @@ const CategoryCard = React.memo(
|
|||||||
to={`/${item.id}`}
|
to={`/${item.id}`}
|
||||||
className="text-white font-semibold mt-3 item-title hover:text-white hover:cursor-pointer line-clamp-1"
|
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>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { FaHistory, FaChevronLeft, FaChevronRight } from "react-icons/fa";
|
|||||||
import { useLanguage } from "@/src/context/LanguageContext";
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
|
||||||
const ContinueWatching = () => {
|
const ContinueWatching = () => {
|
||||||
const [watchList, setWatchList] = useState([]);
|
const [watchList, setWatchList] = useState([]);
|
||||||
@@ -92,9 +93,9 @@ const ContinueWatching = () => {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${item?.poster}`}
|
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"
|
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"
|
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">
|
<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">
|
<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">
|
<p className="text-white text-[15px] font-bold text-left truncate mb-1.5 max-[450px]:text-sm drop-shadow-lg">
|
||||||
{language === "EN"
|
{getSafeTitle(item?.title, language, item?.japanese_title)}
|
||||||
? item?.title
|
|
||||||
: item?.japanese_title}
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-200 text-[13px] font-semibold text-left max-[450px]:text-[12px] drop-shadow-md">
|
<p className="text-gray-200 text-[13px] font-semibold text-left max-[450px]:text-[12px] drop-shadow-md">
|
||||||
Episode {item.episodeNum}
|
Episode {item.episodeNum}
|
||||||
|
|||||||
@@ -1,18 +1,39 @@
|
|||||||
import logoTitle from "@/src/config/logoTitle.js";
|
import logoTitle from "@/src/config/logoTitle.js";
|
||||||
import website_name from "@/src/config/website.js";
|
import website_name from "@/src/config/website.js";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { FaDiscord, FaTelegram } from "react-icons/fa";
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="w-full mt-16">
|
<footer className="w-full mt-16">
|
||||||
{/* Logo Section */}
|
{/* Logo Section */}
|
||||||
<div className="max-w-[1920px] mx-auto px-4">
|
<div className="max-w-[1920px] mx-auto px-4">
|
||||||
<div className="flex justify-center sm:justify-start items-center gap-6">
|
<div className="flex flex-col sm:flex-row justify-between items-center gap-6">
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
<img
|
<img
|
||||||
src="/footer.png"
|
src="/footer.png"
|
||||||
alt={logoTitle}
|
alt={logoTitle}
|
||||||
className="h-[100px] w-[200px] object-contain"
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -59,11 +59,13 @@ export default function Player({
|
|||||||
animeInfo,
|
animeInfo,
|
||||||
episodeNum,
|
episodeNum,
|
||||||
streamInfo,
|
streamInfo,
|
||||||
|
serverName,
|
||||||
}) {
|
}) {
|
||||||
const artRef = useRef(null);
|
const artRef = useRef(null);
|
||||||
const leftAtRef = useRef(0);
|
const leftAtRef = useRef(0);
|
||||||
const proxy = import.meta.env.VITE_PROXY_URL;
|
const proxy = import.meta.env.VITE_PROXY_URL;
|
||||||
const m3u8proxy = import.meta.env.VITE_M3U8_PROXY_URL?.split(",") || [];
|
const m3u8proxy = import.meta.env.VITE_M3U8_PROXY_URL?.split(",") || [];
|
||||||
|
const m3u8proxyHD3 = import.meta.env.VITE_M3U8_PROXY_HD3;
|
||||||
const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
|
const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
|
||||||
episodes?.findIndex(
|
episodes?.findIndex(
|
||||||
(episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
|
(episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
|
||||||
@@ -212,14 +214,17 @@ export default function Player({
|
|||||||
const iframeUrl = streamInfo?.streamingLink?.iframe;
|
const iframeUrl = streamInfo?.streamingLink?.iframe;
|
||||||
const headers = {};
|
const headers = {};
|
||||||
headers.referer = new URL(iframeUrl).origin + "/";
|
headers.referer = new URL(iframeUrl).origin + "/";
|
||||||
console.log(m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)] +
|
const finalProxy = (serverName === "hd-3" && m3u8proxyHD3)
|
||||||
|
? m3u8proxyHD3
|
||||||
|
: m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)];
|
||||||
|
console.log(finalProxy +
|
||||||
encodeURIComponent(streamUrl) +
|
encodeURIComponent(streamUrl) +
|
||||||
"&headers=" +
|
"&headers=" +
|
||||||
encodeURIComponent(JSON.stringify(headers)));
|
encodeURIComponent(JSON.stringify(headers)));
|
||||||
|
|
||||||
const art = new Artplayer({
|
const art = new Artplayer({
|
||||||
url:
|
url:
|
||||||
m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)] +
|
finalProxy +
|
||||||
encodeURIComponent(streamUrl) +
|
encodeURIComponent(streamUrl) +
|
||||||
"&headers=" +
|
"&headers=" +
|
||||||
encodeURIComponent(JSON.stringify(headers)),
|
encodeURIComponent(JSON.stringify(headers)),
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ import {
|
|||||||
faMicrophone,
|
faMicrophone,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
|
|
||||||
function Qtip({ id }) {
|
function Qtip({ id }) {
|
||||||
|
const { language } = useLanguage();
|
||||||
const [qtip, setQtip] = useState(null);
|
const [qtip, setQtip] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@@ -38,7 +41,7 @@ function Qtip({ id }) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="w-full flex flex-col justify-start gap-y-2">
|
<div className="w-full flex flex-col justify-start gap-y-2">
|
||||||
<h1 className="text-xl font-semibold text-white text-[13px] leading-6">
|
<h1 className="text-xl font-semibold text-white text-[13px] leading-6">
|
||||||
{qtip.title}
|
{getSafeTitle(qtip.title, language, qtip.japaneseTitle)}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="w-full flex items-center relative mt-2">
|
<div className="w-full flex items-center relative mt-2">
|
||||||
{qtip?.rating && (
|
{qtip?.rating && (
|
||||||
|
|||||||
@@ -36,12 +36,14 @@ function Servers({
|
|||||||
setActiveServerId(matchingServer.data_id);
|
setActiveServerId(matchingServer.data_id);
|
||||||
setActiveServerType(matchingServer.type);
|
setActiveServerType(matchingServer.type);
|
||||||
} else if (servers && servers.length > 0) {
|
} else if (servers && servers.length > 0) {
|
||||||
setActiveServerId(servers[0].data_id);
|
const defaultServer = servers.find(s => s.serverName === "HD-2") || servers[0];
|
||||||
setActiveServerType(servers[0].type);
|
setActiveServerId(defaultServer.data_id);
|
||||||
|
setActiveServerType(defaultServer.type);
|
||||||
}
|
}
|
||||||
} else if (servers && servers.length > 0) {
|
} else if (servers && servers.length > 0) {
|
||||||
setActiveServerId(servers[0].data_id);
|
const defaultServer = servers.find(s => s.serverName === "HD-2") || servers[0];
|
||||||
setActiveServerType(servers[0].type);
|
setActiveServerId(defaultServer.data_id);
|
||||||
|
setActiveServerType(defaultServer.type);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [servers]);
|
}, [servers]);
|
||||||
@@ -77,8 +79,7 @@ function Servers({
|
|||||||
</div>
|
</div>
|
||||||
<div className="bg-[#1f1f1f] flex flex-col max-[600px]:rounded-lg max-[600px]:p-2">
|
<div className="bg-[#1f1f1f] flex flex-col max-[600px]:rounded-lg max-[600px]:p-2">
|
||||||
{rawServers.length > 0 && (
|
{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 ${
|
<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
|
||||||
dubServers.length === 0 || subServers.length === 0
|
|
||||||
? "h-1/2"
|
? "h-1/2"
|
||||||
: "h-full"
|
: "h-full"
|
||||||
}`}>
|
}`}>
|
||||||
@@ -93,8 +94,7 @@ function Servers({
|
|||||||
{rawServers.map((item, index) => (
|
{rawServers.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${
|
className={`px-6 py-[5px] rounded-lg cursor-pointer ${activeServerId === item?.data_id
|
||||||
activeServerId === item?.data_id
|
|
||||||
? "bg-[#e0e0e0] text-black"
|
? "bg-[#e0e0e0] text-black"
|
||||||
: "bg-[#373737] text-white"
|
: "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`}
|
||||||
@@ -109,8 +109,7 @@ function Servers({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{subServers.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 ${
|
<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"
|
||||||
dubServers.length === 0 ? "h-1/2" : "h-full"
|
|
||||||
}`}>
|
}`}>
|
||||||
<div className="flex items-center gap-x-2 min-w-[65px]">
|
<div className="flex items-center gap-x-2 min-w-[65px]">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
@@ -123,8 +122,7 @@ function Servers({
|
|||||||
{subServers.map((item, index) => (
|
{subServers.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${
|
className={`px-6 py-[5px] rounded-lg cursor-pointer ${activeServerId === item?.data_id
|
||||||
activeServerId === item?.data_id
|
|
||||||
? "bg-[#e0e0e0] text-black"
|
? "bg-[#e0e0e0] text-black"
|
||||||
: "bg-[#373737] text-white"
|
: "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`}
|
||||||
@@ -139,8 +137,7 @@ function Servers({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{dubServers.length > 0 && (
|
{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 ${
|
<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"
|
||||||
subServers.length === 0 ? "h-1/2" : "h-full"
|
|
||||||
}`}>
|
}`}>
|
||||||
<div className="flex items-center gap-x-2 min-w-[65px]">
|
<div className="flex items-center gap-x-2 min-w-[65px]">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
@@ -153,8 +150,7 @@ function Servers({
|
|||||||
{dubServers.map((item, index) => (
|
{dubServers.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`px-6 py-[5px] rounded-lg cursor-pointer ${
|
className={`px-6 py-[5px] rounded-lg cursor-pointer ${activeServerId === item?.data_id
|
||||||
activeServerId === item?.data_id
|
|
||||||
? "bg-[#e0e0e0] text-black"
|
? "bg-[#e0e0e0] text-black"
|
||||||
: "bg-[#373737] text-white"
|
: "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`}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useLanguage } from "@/src/context/LanguageContext";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } 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";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
|
||||||
function Sidecard({ data, label, className }) {
|
function Sidecard({ data, label, className }) {
|
||||||
const { language } = useLanguage();
|
const { language } = useLanguage();
|
||||||
@@ -50,12 +51,10 @@ function Sidecard({ data, label, className }) {
|
|||||||
<div className="flex items-start gap-3 p-2 rounded-lg transition-colors hover:bg-[#1f1f1f]">
|
<div className="flex items-start gap-3 p-2 rounded-lg transition-colors hover:bg-[#1f1f1f]">
|
||||||
{hoveredItem === item.id + index && window.innerWidth > 1024 && (
|
{hoveredItem === item.id + index && window.innerWidth > 1024 && (
|
||||||
<div
|
<div
|
||||||
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition} ${
|
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition} ${tooltipPosition === "top-1/2"
|
||||||
tooltipPosition === "top-1/2"
|
|
||||||
? "translate-y-[50px]"
|
? "translate-y-[50px]"
|
||||||
: "translate-y-[-50px]"
|
: "translate-y-[-50px]"
|
||||||
} z-[100000] transform transition-all duration-300 ease-in-out ${
|
} z-[100000] transform transition-all duration-300 ease-in-out ${hoveredItem === item.id + index
|
||||||
hoveredItem === item.id + index
|
|
||||||
? "opacity-100 translate-y-0"
|
? "opacity-100 translate-y-0"
|
||||||
: "opacity-0 translate-y-2"
|
: "opacity-0 translate-y-2"
|
||||||
}`}
|
}`}
|
||||||
@@ -65,12 +64,12 @@ function Sidecard({ data, label, className }) {
|
|||||||
)}
|
)}
|
||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
src={`${item.poster}`}
|
||||||
alt={item.title}
|
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||||
className="w-[50px] h-[70px] rounded object-cover"
|
className="w-[50px] h-[70px] rounded object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-1.5 flex-1 min-w-0">
|
<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">
|
<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>
|
</span>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{item.tvInfo?.sub && (
|
{item.tvInfo?.sub && (
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ import { useEffect, useState } from "react";
|
|||||||
import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
|
import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
|
||||||
import { FaChevronRight } from "react-icons/fa";
|
import { FaChevronRight } from "react-icons/fa";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
|
|
||||||
function Suggestion({ keyword, className, onSuggestionClick }) {
|
function Suggestion({ keyword, className, onSuggestionClick }) {
|
||||||
|
const { language } = useLanguage();
|
||||||
const [suggestion, setSuggestion] = useState([]);
|
const [suggestion, setSuggestion] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@@ -31,8 +34,7 @@ function Suggestion({ keyword, className, onSuggestionClick }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`bg-zinc-900 ${className} flex ${
|
className={`bg-zinc-900 ${className} flex ${loading ? "justify-center py-4" : "justify-start"
|
||||||
loading ? "justify-center py-4" : "justify-start"
|
|
||||||
} ${!suggestion ? "p-2" : "justify-start"} items-center rounded-lg`}
|
} ${!suggestion ? "p-2" : "justify-start"} items-center rounded-lg`}
|
||||||
style={{
|
style={{
|
||||||
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
|
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
|
||||||
@@ -61,20 +63,15 @@ function Suggestion({ keyword, className, onSuggestionClick }) {
|
|||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
src={`${item.poster}`}
|
||||||
className="w-[45px] h-[65px] flex-shrink-0 object-cover rounded-md shadow-lg"
|
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) => {
|
onError={(e) => {
|
||||||
e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
|
e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-y-[2px]">
|
<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">
|
<h1 className="line-clamp-1 leading-5 font-semibold text-[14px] text-gray-100 group-hover:text-white">
|
||||||
{item.title || "N/A"}
|
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||||
</h1>
|
|
||||||
)}
|
|
||||||
{item?.japanese_title && (
|
|
||||||
<h1 className="line-clamp-1 leading-4 text-[12px] font-normal text-gray-400">
|
|
||||||
{item.japanese_title || "N/A"}
|
|
||||||
</h1>
|
</h1>
|
||||||
)}
|
)}
|
||||||
{(item?.releaseDate || item?.showType || item?.duration) && (
|
{(item?.releaseDate || item?.showType || item?.duration) && (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useLanguage } from "@/src/context/LanguageContext";
|
|||||||
import { Link, useNavigate } from "react-router-dom";
|
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";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
|
||||||
function Topten({ data, className }) {
|
function Topten({ data, className }) {
|
||||||
const { language } = useLanguage();
|
const { language } = useLanguage();
|
||||||
@@ -56,8 +57,7 @@ function Topten({ data, className }) {
|
|||||||
{["today", "week", "month"].map((period) => (
|
{["today", "week", "month"].map((period) => (
|
||||||
<li
|
<li
|
||||||
key={period}
|
key={period}
|
||||||
className={`cursor-pointer p-1.5 px-4 transition-all duration-200 ${
|
className={`cursor-pointer p-1.5 px-4 transition-all duration-200 ${activePeriod === period
|
||||||
activePeriod === period
|
|
||||||
? "bg-white text-black font-medium"
|
? "bg-white text-black font-medium"
|
||||||
: "text-gray-400 hover:text-white hover:bg-[#2a2a2a]"
|
: "text-gray-400 hover:text-white hover:bg-[#2a2a2a]"
|
||||||
}`}
|
}`}
|
||||||
@@ -78,8 +78,7 @@ function Topten({ data, className }) {
|
|||||||
ref={(el) => (cardRefs.current[index] = el)}
|
ref={(el) => (cardRefs.current[index] = el)}
|
||||||
>
|
>
|
||||||
<h1
|
<h1
|
||||||
className={`font-bold text-2xl transition-colors ${
|
className={`font-bold text-2xl transition-colors ${index < 3
|
||||||
index < 3
|
|
||||||
? "text-white border-b-2 border-white pb-0.5"
|
? "text-white border-b-2 border-white pb-0.5"
|
||||||
: "text-gray-600"
|
: "text-gray-600"
|
||||||
} max-[350px]:hidden`}
|
} max-[350px]:hidden`}
|
||||||
@@ -97,7 +96,7 @@ function Topten({ data, className }) {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
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]"
|
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)}
|
||||||
@@ -109,14 +108,12 @@ function Topten({ data, className }) {
|
|||||||
window.innerWidth > 1024 && (
|
window.innerWidth > 1024 && (
|
||||||
<div
|
<div
|
||||||
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition}
|
className={`absolute ${tooltipPosition} ${tooltipHorizontalPosition}
|
||||||
${
|
${tooltipPosition === "top-1/2"
|
||||||
tooltipPosition === "top-1/2"
|
|
||||||
? "translate-y-[50px]"
|
? "translate-y-[50px]"
|
||||||
: "translate-y-[-50px]"
|
: "translate-y-[-50px]"
|
||||||
}
|
}
|
||||||
z-[100000] transform transition-all duration-300 ease-in-out
|
z-[100000] transform transition-all duration-300 ease-in-out
|
||||||
${
|
${hoveredItem === item.id + index
|
||||||
hoveredItem === item.id + index
|
|
||||||
? "opacity-100 translate-y-0"
|
? "opacity-100 translate-y-0"
|
||||||
: "opacity-0 translate-y-2"
|
: "opacity-0 translate-y-2"
|
||||||
}`}
|
}`}
|
||||||
@@ -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]"
|
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}
|
{getSafeTitle(item.title, language, item.japanese_title)}
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex flex-wrap items-center w-fit space-x-2 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 && (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
faMicrophone,
|
faMicrophone,
|
||||||
faFire
|
faFire
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
|
||||||
const Trending = ({ trending, className }) => {
|
const Trending = ({ trending, className }) => {
|
||||||
const { language } = useLanguage();
|
const { language } = useLanguage();
|
||||||
@@ -29,7 +30,7 @@ const Trending = ({ trending, className }) => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<img
|
<img
|
||||||
src={item.poster}
|
src={item.poster}
|
||||||
alt={item.title}
|
alt={getSafeTitle(item.title, language, item.japanese_title)}
|
||||||
className="w-[50px] h-[70px] rounded object-cover"
|
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">
|
<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>
|
||||||
<div className="flex flex-col gap-1.5 flex-1 min-w-0">
|
<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">
|
<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>
|
</span>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{item.tvInfo?.sub && (
|
{item.tvInfo?.sub && (
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export const useWatch = (animeId, initialEpisodeId) => {
|
|||||||
filteredServers.find(s => s.serverName === savedServerName && s.type === savedServerType) ||
|
filteredServers.find(s => s.serverName === savedServerName && s.type === savedServerType) ||
|
||||||
filteredServers.find(s => s.serverName === savedServerName) ||
|
filteredServers.find(s => s.serverName === savedServerName) ||
|
||||||
filteredServers.find(s => s.type === savedServerType && ["HD-1", "HD-2", "HD-3", "HD-4"].includes(s.serverName)) ||
|
filteredServers.find(s => s.type === savedServerType && ["HD-1", "HD-2", "HD-3", "HD-4"].includes(s.serverName)) ||
|
||||||
|
filteredServers.find(s => s.serverName === "HD-2") ||
|
||||||
filteredServers[0];
|
filteredServers[0];
|
||||||
|
|
||||||
setServers(filteredServers);
|
setServers(filteredServers);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import Error from "@/src/components/error/Error";
|
|||||||
import { useLanguage } from "@/src/context/LanguageContext";
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
import { useHomeInfo } from "@/src/context/HomeInfoContext";
|
import { useHomeInfo } from "@/src/context/HomeInfoContext";
|
||||||
import Voiceactor from "@/src/components/voiceactor/Voiceactor";
|
import Voiceactor from "@/src/components/voiceactor/Voiceactor";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
|
||||||
function InfoItem({ label, value, isProducer = true }) {
|
function InfoItem({ label, value, isProducer = true }) {
|
||||||
return (
|
return (
|
||||||
@@ -111,12 +112,13 @@ function AnimeInfo({ random = false }) {
|
|||||||
}, [id, random]);
|
}, [id, random]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (animeInfo && location.pathname === `/${animeInfo.id}`) {
|
if (animeInfo && location.pathname === `/${animeInfo.id}`) {
|
||||||
document.title = `Watch ${animeInfo.title} English Sub/Dub online Free on ${website_name}`;
|
const safeTitle = getSafeTitle(animeInfo.title, language, animeInfo.japanese_title);
|
||||||
|
document.title = `Watch ${safeTitle} English Sub/Dub online Free on ${website_name}`;
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
document.title = `${website_name} | Free anime streaming platform`;
|
document.title = `${website_name} | Free anime streaming platform`;
|
||||||
};
|
};
|
||||||
}, [animeInfo]);
|
}, [animeInfo, language]);
|
||||||
if (loading) return <Loader type="animeInfo" />;
|
if (loading) return <Loader type="animeInfo" />;
|
||||||
if (error) {
|
if (error) {
|
||||||
return <Error />;
|
return <Error />;
|
||||||
@@ -126,6 +128,8 @@ function AnimeInfo({ random = false }) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const { title, japanese_title, poster, animeInfo: info } = animeInfo;
|
const { title, japanese_title, poster, animeInfo: info } = animeInfo;
|
||||||
|
const safeTitle = getSafeTitle(title, language, japanese_title);
|
||||||
|
|
||||||
const tags = [
|
const tags = [
|
||||||
{
|
{
|
||||||
condition: info.tvInfo?.rating,
|
condition: info.tvInfo?.rating,
|
||||||
@@ -165,7 +169,7 @@ function AnimeInfo({ random = false }) {
|
|||||||
<div className="relative w-[130px] xs:w-[150px] aspect-[2/3] rounded-xl overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.3)]">
|
<div className="relative w-[130px] xs:w-[150px] aspect-[2/3] rounded-xl overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.3)]">
|
||||||
<img
|
<img
|
||||||
src={`${poster}`}
|
src={`${poster}`}
|
||||||
alt={`${title} Poster`}
|
alt={`${safeTitle} Poster`}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
{animeInfo.adultContent && (
|
{animeInfo.adultContent && (
|
||||||
@@ -181,7 +185,7 @@ function AnimeInfo({ random = false }) {
|
|||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<h1 className="text-lg xs:text-xl font-bold tracking-tight truncate">
|
<h1 className="text-lg xs:text-xl font-bold tracking-tight truncate">
|
||||||
{language === "EN" ? title : japanese_title}
|
{safeTitle}
|
||||||
</h1>
|
</h1>
|
||||||
{language === "EN" && japanese_title && (
|
{language === "EN" && japanese_title && (
|
||||||
<p className="text-white/50 text-[11px] xs:text-xs truncate">JP Title: {japanese_title}</p>
|
<p className="text-white/50 text-[11px] xs:text-xs truncate">JP Title: {japanese_title}</p>
|
||||||
@@ -310,7 +314,7 @@ function AnimeInfo({ random = false }) {
|
|||||||
<div className="relative w-[220px] lg:w-[260px] aspect-[2/3] rounded-2xl overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.3)]">
|
<div className="relative w-[220px] lg:w-[260px] aspect-[2/3] rounded-2xl overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.3)]">
|
||||||
<img
|
<img
|
||||||
src={`${poster}`}
|
src={`${poster}`}
|
||||||
alt={`${title} Poster`}
|
alt={`${safeTitle} Poster`}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
{animeInfo.adultContent && (
|
{animeInfo.adultContent && (
|
||||||
@@ -326,7 +330,7 @@ function AnimeInfo({ random = false }) {
|
|||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h1 className="text-3xl lg:text-4xl font-bold tracking-tight truncate">
|
<h1 className="text-3xl lg:text-4xl font-bold tracking-tight truncate">
|
||||||
{language === "EN" ? title : japanese_title}
|
{safeTitle}
|
||||||
</h1>
|
</h1>
|
||||||
{language === "EN" && japanese_title && (
|
{language === "EN" && japanese_title && (
|
||||||
<p className="text-white/50 text-sm lg:text-base truncate">JP Title: {japanese_title}</p>
|
<p className="text-white/50 text-sm lg:text-base truncate">JP Title: {japanese_title}</p>
|
||||||
@@ -454,8 +458,7 @@ function AnimeInfo({ random = false }) {
|
|||||||
<Link
|
<Link
|
||||||
to={`/${season.id}`}
|
to={`/${season.id}`}
|
||||||
key={index}
|
key={index}
|
||||||
className={`relative w-full aspect-[3/1] sm:aspect-[3/1] rounded-lg overflow-hidden cursor-pointer group ${
|
className={`relative w-full aspect-[3/1] sm:aspect-[3/1] rounded-lg overflow-hidden cursor-pointer group ${currentId === String(season.id)
|
||||||
currentId === String(season.id)
|
|
||||||
? "ring-2 ring-white/40 shadow-lg shadow-white/10"
|
? "ring-2 ring-white/40 shadow-lg shadow-white/10"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
@@ -463,8 +466,7 @@ function AnimeInfo({ random = false }) {
|
|||||||
<img
|
<img
|
||||||
src={season.season_poster}
|
src={season.season_poster}
|
||||||
alt={season.season}
|
alt={season.season}
|
||||||
className={`w-full h-full object-cover scale-150 ${
|
className={`w-full h-full object-cover scale-150 ${currentId === String(season.id)
|
||||||
currentId === String(season.id)
|
|
||||||
? "opacity-50"
|
? "opacity-50"
|
||||||
: "opacity-40"
|
: "opacity-40"
|
||||||
}`}
|
}`}
|
||||||
@@ -478,15 +480,13 @@ function AnimeInfo({ random = false }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Dark Gradient Overlay */}
|
{/* Dark Gradient Overlay */}
|
||||||
<div className={`absolute inset-0 z-20 bg-gradient-to-r ${
|
<div className={`absolute inset-0 z-20 bg-gradient-to-r ${currentId === String(season.id)
|
||||||
currentId === String(season.id)
|
|
||||||
? "from-black/50 to-transparent"
|
? "from-black/50 to-transparent"
|
||||||
: "from-black/40 to-transparent"
|
: "from-black/40 to-transparent"
|
||||||
}`} />
|
}`} />
|
||||||
{/* Title Container */}
|
{/* Title Container */}
|
||||||
<div className="absolute inset-0 z-30 flex items-center justify-center">
|
<div className="absolute inset-0 z-30 flex items-center justify-center">
|
||||||
<p className={`text-[14px] sm:text-[16px] md:text-[18px] font-bold text-center px-2 sm:px-4 transition-colors duration-300 ${
|
<p className={`text-[14px] sm:text-[16px] md:text-[18px] font-bold text-center px-2 sm:px-4 transition-colors duration-300 ${currentId === String(season.id)
|
||||||
currentId === String(season.id)
|
|
||||||
? "text-white"
|
? "text-white"
|
||||||
: "text-white/90 group-hover:text-white"
|
: "text-white/90 group-hover:text-white"
|
||||||
}`}>
|
}`}>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SidecardLoader from "@/src/components/Loader/Sidecard.loader";
|
|||||||
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";
|
||||||
|
import getSafeTitle from "@/src/utils/getSafetitle";
|
||||||
|
|
||||||
export default function Watch() {
|
export default function Watch() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -101,16 +102,20 @@ export default function Watch() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [episodeId, animeId, navigate, episodes]);
|
}, [episodeId, animeId, navigate, episodes]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ... inside Watch component ...
|
||||||
// Update document title
|
// Update document title
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (animeInfo) {
|
if (animeInfo) {
|
||||||
document.title = `Watch ${animeInfo.title} English Sub/Dub online Free on ${website_name}`;
|
const safeTitle = getSafeTitle(animeInfo.title, language, animeInfo.japanese_title);
|
||||||
|
document.title = `Watch ${safeTitle} English Sub/Dub online Free on ${website_name}`;
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
document.title = `${website_name} | Free anime streaming platform`;
|
document.title = `${website_name} | Free anime streaming platform`;
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [animeId]);
|
}, [animeId, animeInfo, language]);
|
||||||
|
|
||||||
// Redirect if no episodes
|
// Redirect if no episodes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -184,8 +189,7 @@ export default function Watch() {
|
|||||||
function Tag({ bgColor, index, icon, text }) {
|
function Tag({ bgColor, index, icon, text }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex space-x-1 justify-center items-center px-[4px] py-[1px] text-black font-semibold text-[13px] ${
|
className={`flex space-x-1 justify-center items-center px-[4px] py-[1px] text-black font-semibold text-[13px] ${index === 0 ? "rounded-l-[4px]" : "rounded-none"
|
||||||
index === 0 ? "rounded-l-[4px]" : "rounded-none"
|
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: bgColor }}
|
style={{ backgroundColor: bgColor }}
|
||||||
>
|
>
|
||||||
@@ -361,8 +365,7 @@ export default function Watch() {
|
|||||||
<Link
|
<Link
|
||||||
to={`/${season.id}`}
|
to={`/${season.id}`}
|
||||||
key={index}
|
key={index}
|
||||||
className={`relative w-full aspect-[3/1] rounded-lg overflow-hidden cursor-pointer group ${
|
className={`relative w-full aspect-[3/1] rounded-lg overflow-hidden cursor-pointer group ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "ring-2 ring-white/40 shadow-lg shadow-white/10"
|
? "ring-2 ring-white/40 shadow-lg shadow-white/10"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
@@ -370,8 +373,7 @@ export default function Watch() {
|
|||||||
<img
|
<img
|
||||||
src={season.season_poster}
|
src={season.season_poster}
|
||||||
alt={season.season}
|
alt={season.season}
|
||||||
className={`w-full h-full object-cover scale-150 ${
|
className={`w-full h-full object-cover scale-150 ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "opacity-50"
|
? "opacity-50"
|
||||||
: "opacity-40 group-hover:opacity-50 transition-opacity"
|
: "opacity-40 group-hover:opacity-50 transition-opacity"
|
||||||
}`}
|
}`}
|
||||||
@@ -385,15 +387,13 @@ export default function Watch() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Dark Gradient Overlay */}
|
{/* Dark Gradient Overlay */}
|
||||||
<div className={`absolute inset-0 z-20 bg-gradient-to-r ${
|
<div className={`absolute inset-0 z-20 bg-gradient-to-r ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "from-black/50 to-transparent"
|
? "from-black/50 to-transparent"
|
||||||
: "from-black/40 to-transparent"
|
: "from-black/40 to-transparent"
|
||||||
}`} />
|
}`} />
|
||||||
{/* Title Container */}
|
{/* Title Container */}
|
||||||
<div className="absolute inset-0 z-30 flex items-center justify-center">
|
<div className="absolute inset-0 z-30 flex items-center justify-center">
|
||||||
<p className={`text-[14px] font-bold text-center px-2 transition-colors duration-300 ${
|
<p className={`text-[14px] font-bold text-center px-2 transition-colors duration-300 ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "text-white"
|
? "text-white"
|
||||||
: "text-white/90 group-hover:text-white"
|
: "text-white/90 group-hover:text-white"
|
||||||
}`}>
|
}`}>
|
||||||
@@ -443,7 +443,7 @@ export default function Watch() {
|
|||||||
className="group"
|
className="group"
|
||||||
>
|
>
|
||||||
<h1 className="text-[28px] font-medium text-white leading-tight group-hover:text-gray-300 transition-colors max-[600px]:text-[20px]">
|
<h1 className="text-[28px] font-medium text-white leading-tight group-hover:text-gray-300 transition-colors max-[600px]:text-[20px]">
|
||||||
{language ? animeInfo?.title : animeInfo?.japanese_title}
|
{getSafeTitle(animeInfo.title, language, animeInfo.japanese_title)}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center gap-1.5 mt-1 text-gray-400 text-sm group-hover:text-white transition-colors max-[600px]:text-[12px] max-[600px]:mt-0.5">
|
<div className="flex items-center gap-1.5 mt-1 text-gray-400 text-sm group-hover:text-white transition-colors max-[600px]:text-[12px] max-[600px]:mt-0.5">
|
||||||
<span>View Details</span>
|
<span>View Details</span>
|
||||||
@@ -502,8 +502,7 @@ export default function Watch() {
|
|||||||
<Link
|
<Link
|
||||||
to={`/${season.id}`}
|
to={`/${season.id}`}
|
||||||
key={index}
|
key={index}
|
||||||
className={`relative w-full aspect-[3/1] rounded-lg overflow-hidden cursor-pointer group ${
|
className={`relative w-full aspect-[3/1] rounded-lg overflow-hidden cursor-pointer group ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "ring-2 ring-white/40 shadow-lg shadow-white/10"
|
? "ring-2 ring-white/40 shadow-lg shadow-white/10"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
@@ -511,8 +510,7 @@ export default function Watch() {
|
|||||||
<img
|
<img
|
||||||
src={season.season_poster}
|
src={season.season_poster}
|
||||||
alt={season.season}
|
alt={season.season}
|
||||||
className={`w-full h-full object-cover scale-150 ${
|
className={`w-full h-full object-cover scale-150 ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "opacity-50"
|
? "opacity-50"
|
||||||
: "opacity-40 group-hover:opacity-50 transition-opacity"
|
: "opacity-40 group-hover:opacity-50 transition-opacity"
|
||||||
}`}
|
}`}
|
||||||
@@ -526,15 +524,13 @@ export default function Watch() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Dark Gradient Overlay */}
|
{/* Dark Gradient Overlay */}
|
||||||
<div className={`absolute inset-0 z-20 bg-gradient-to-r ${
|
<div className={`absolute inset-0 z-20 bg-gradient-to-r ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "from-black/50 to-transparent"
|
? "from-black/50 to-transparent"
|
||||||
: "from-black/40 to-transparent"
|
: "from-black/40 to-transparent"
|
||||||
}`} />
|
}`} />
|
||||||
{/* Title Container */}
|
{/* Title Container */}
|
||||||
<div className="absolute inset-0 z-30 flex items-center justify-center">
|
<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 ${
|
<p className={`text-[14px] sm:text-[16px] font-bold text-center px-2 sm:px-4 transition-colors duration-300 ${animeId === String(season.id)
|
||||||
animeId === String(season.id)
|
|
||||||
? "text-white"
|
? "text-white"
|
||||||
: "text-white/90 group-hover:text-white"
|
: "text-white/90 group-hover:text-white"
|
||||||
}`}>
|
}`}>
|
||||||
|
|||||||
18
src/utils/getSafetitle.js
Normal file
18
src/utils/getSafetitle.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default function getSafeTitle(title, language = 'EN', jpTitle = '') {
|
||||||
|
if (!title) return '';
|
||||||
|
|
||||||
|
// If title is already a string, return it
|
||||||
|
if (typeof title === 'string') return title;
|
||||||
|
|
||||||
|
// If title is an object, extract based on language preference
|
||||||
|
if (typeof title === 'object') {
|
||||||
|
if (language === 'EN') {
|
||||||
|
return title.english || title.romaji || title.userPreferred || title.native || jpTitle || 'Unknown Title';
|
||||||
|
} else {
|
||||||
|
// For JP/other languages, prefer native or romaji
|
||||||
|
return title.native || title.romaji || title.userPreferred || title.english || jpTitle || 'Unknown Title';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unknown Title';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user