From b1636ce8c4a2b74078787f8df20357f60f648a5c Mon Sep 17 00:00:00 2001 From: Tejas Panchal Date: Thu, 19 Feb 2026 03:01:34 +0530 Subject: [PATCH] resolved info and watch --- src/components/ui/InfoTag/InfoTag.jsx | 18 + src/pages/animeInfo/AnimeInfo.jsx | 692 ++++++++++---------------- src/pages/watch/Watch.jsx | 653 ++++++++---------------- 3 files changed, 477 insertions(+), 886 deletions(-) create mode 100644 src/components/ui/InfoTag/InfoTag.jsx diff --git a/src/components/ui/InfoTag/InfoTag.jsx b/src/components/ui/InfoTag/InfoTag.jsx new file mode 100644 index 0000000..99ac2f6 --- /dev/null +++ b/src/components/ui/InfoTag/InfoTag.jsx @@ -0,0 +1,18 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import React from "react"; + +const InfoTag = ({ icon, text, bgColor, className = "" }) => { + if (!text) return null; + + return ( +
+ {icon && } +

{text}

+
+ ); +}; + +export default React.memo(InfoTag); diff --git a/src/pages/animeInfo/AnimeInfo.jsx b/src/pages/animeInfo/AnimeInfo.jsx index e10e220..5d217a6 100644 --- a/src/pages/animeInfo/AnimeInfo.jsx +++ b/src/pages/animeInfo/AnimeInfo.jsx @@ -5,18 +5,16 @@ import { faClosedCaptioning, faMicrophone, } from "@fortawesome/free-solid-svg-icons"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useMemo } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; -import website_name from "@/src/config/website"; import CategoryCard from "@/src/components/categorycard/CategoryCard"; -import Sidecard from "@/src/components/sidecard/Sidecard"; import Loader from "@/src/components/Loader/Loader"; import Error from "@/src/components/error/Error"; import { useLanguage } from "@/src/context/LanguageContext"; -import { useHomeInfo } from "@/src/context/HomeInfoContext"; import Voiceactor from "@/src/components/voiceactor/Voiceactor"; import getSafeTitle from "@/src/utils/getSafetitle"; import { Helmet } from 'react-helmet-async'; +import InfoTag from "@/src/components/ui/InfoTag/InfoTag"; import { generateDescription, generateKeywords, @@ -27,536 +25,356 @@ import { optimizeTitle, } from '@/src/utils/seo.utils'; -function InfoItem({ label, value, isProducer = true }) { - return ( - value && ( -
- {`${label}: `} - - {Array.isArray(value) ? ( - value.map((item, index) => - isProducer ? ( - :;,.?/\\|{}[\]`~*_]/g, "") - .split(" ") - .join("-") - .replace(/-+/g, "-")}`} - key={index} - className="cursor-pointer transition-colors duration-300 hover:text-gray-300" - > - {item} - {index < value.length - 1 && ", "} - - ) : ( - - {item} - {index < value.length - 1 && ", "} - - ) - ) - ) : isProducer ? ( +const InfoItem = ({ label, value, isProducer = true }) => { + if (!value) return null; + + const renderValue = () => { + if (Array.isArray(value)) { + return value.map((item, index) => ( + + {isProducer ? ( :;,.?/\\|{}[\]`~*_]/g, "") - .split(" ") - .join("-") - .replace(/-+/g, "-")}`} + to={`/producer/${item.replace(/[&'"^%$#@!()+=<>:;,.?/\\|{}[\]`~*_]/g, "").split(" ").join("-").replace(/-+/g, "-")}`} className="cursor-pointer transition-colors duration-300 hover:text-gray-300" > - {value} + {item} ) : ( - {value} + item )} + {index < value.length - 1 && ", "} -
- ) - ); -} + )); + } + + if (isProducer) { + return ( + :;,.?/\\|{}[\]`~*_]/g, "").split(" ").join("-").replace(/-+/g, "-")}`} + className="cursor-pointer transition-colors duration-300 hover:text-gray-300" + > + {value} + + ); + } + + return value; + }; -function Tag({ bgColor, index, icon, text }) { return ( -
- {icon && } -

{text}

+
+ {`${label}: `} + {renderValue()}
); -} +}; + +const Synopsis = ({ text, isFull, onToggle, isMobile = false }) => { + if (!text) return null; + const limit = isMobile ? 150 : 270; + const isTooLong = text.length > limit; + + return ( +
+ {isTooLong ? ( + <> + {isFull ? text : (isMobile ?
{text}
: `${text.slice(0, limit)}...`)} + + + ) : text} +
+ ); +}; + +const TagsList = ({ tags }) => ( +
+ {tags.map((tag, index) => tag.condition && ( + + ))} +
+); + +const DetailGrid = ({ info, isMobile = false }) => { + const items = [ + { label: "Japanese", value: info?.Japanese }, + { label: "Synonyms", value: info?.Synonyms }, + { label: "Aired", value: info?.Aired }, + { label: "Premiered", value: info?.Premiered }, + { label: "Duration", value: info?.Duration }, + { label: "Status", value: info?.Status }, + { label: "MAL Score", value: info?.["MAL Score"] }, + ]; + + return ( +
+ {items.map((item, index) => ( + + ))} + + {info?.Genres && ( +
+

Genres

+
+ {info.Genres.map((genre, index) => ( + + {genre} + + ))} +
+
+ )} + +
+ + +
+
+ ); +}; function AnimeInfo({ random = false }) { const { language } = useLanguage(); const { id: paramId } = useParams(); const id = random ? null : paramId; + const { id: currentId } = useParams(); + const navigate = useNavigate(); + const [isFull, setIsFull] = useState(false); const [animeInfo, setAnimeInfo] = useState(null); const [seasons, setSeasons] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const { homeInfo } = useHomeInfo(); - const { id: currentId } = useParams(); - const navigate = useNavigate(); + useEffect(() => { - if (id === "404-not-found-page") { - console.log("404 got!"); - return null; - } else { - const fetchAnimeInfo = async () => { - setLoading(true); - try { - const data = await getAnimeInfo(id, random); - setSeasons(data?.seasons); - setAnimeInfo(data.data); - } catch (err) { - console.error("Error fetching anime info:", err); - setError(err); - } finally { - setLoading(false); - } - }; - fetchAnimeInfo(); - window.scrollTo({ top: 0, behavior: "smooth" }); - } + if (id === "404-not-found-page") return; + + const fetchAnimeInfo = async () => { + setLoading(true); + try { + const data = await getAnimeInfo(id, random); + if (!data?.data) throw new Error("Anime not found"); + setSeasons(data?.seasons); + setAnimeInfo(data.data); + } catch (err) { + console.error("Error fetching anime info:", err); + setError(err); + } finally { + setLoading(false); + } + }; + fetchAnimeInfo(); + window.scrollTo({ top: 0, behavior: "smooth" }); }, [id, random]); + + const seoData = useMemo(() => { + if (!animeInfo) return null; + const { title, japanese_title, poster, animeInfo: info } = animeInfo; + const safeTitle = getSafeTitle(title, language, japanese_title); + return { + safeTitle, + title: optimizeTitle(`Watch ${safeTitle} Sub Dub Online Free`), + description: generateDescription(info?.Overview), + keywords: generateKeywords(animeInfo), + canonical: generateCanonicalUrl(`/${animeInfo.id}`), + ogImage: generateOGImage(poster), + structured: generateAnimeStructuredData(animeInfo), + breadcrumb: generateBreadcrumbStructuredData([ + { name: 'Home', url: '/' }, + { name: animeInfo.title, url: `/${animeInfo.id}` } + ]) + }; + }, [animeInfo, language]); + + const tags = useMemo(() => { + if (!animeInfo?.animeInfo?.tvInfo) return []; + const info = animeInfo.animeInfo; + return [ + { condition: info.tvInfo.rating, text: info.tvInfo.rating, bgColor: "#ffffff" }, + { condition: info.tvInfo.quality, text: info.tvInfo.quality, bgColor: "#FFBADE" }, + { condition: info.tvInfo.sub, text: info.tvInfo.sub, icon: faClosedCaptioning, bgColor: "#B0E3AF" }, + { condition: info.tvInfo.dub, text: info.tvInfo.dub, icon: faMicrophone, bgColor: "#B9E7FF" }, + ]; + }, [animeInfo]); + if (loading) return ; - if (error) { - return ; - } + if (error || (!animeInfo && !loading)) return ; if (!animeInfo) { navigate("/404-not-found-page"); - return undefined; + return null; } - const { title, japanese_title, poster, animeInfo: info } = animeInfo; - const safeTitle = getSafeTitle(title, language, japanese_title); - const displayTitle = safeTitle; // Use safeTitle as the display title - const pageTitle = optimizeTitle(`Watch ${displayTitle} Sub Dub Online Free`); - const pageDescription = generateDescription(info?.Overview); - const pageKeywords = generateKeywords(animeInfo); - const canonicalUrl = generateCanonicalUrl(`/${animeInfo.id}`); - const ogImage = generateOGImage(poster); - - const animeStructuredData = generateAnimeStructuredData(animeInfo); - const breadcrumbData = generateBreadcrumbStructuredData([ - { name: 'Home', url: '/' }, - { name: animeInfo.title, url: `/${animeInfo.id}` } - ]); - - const tags = [ - { - condition: info.tvInfo?.rating, - bgColor: "#ffffff", - text: info.tvInfo.rating, - }, - { - condition: info.tvInfo?.quality, - bgColor: "#FFBADE", - text: info.tvInfo.quality, - }, - { - condition: info.tvInfo?.sub, - icon: faClosedCaptioning, - bgColor: "#B0E3AF", - text: info.tvInfo.sub, - }, - { - condition: info.tvInfo?.dub, - icon: faMicrophone, - bgColor: "#B9E7FF", - text: info.tvInfo.dub, - }, - ]; + const { poster, japanese_title, animeInfo: info } = animeInfo; + const isAiring = animeInfo?.animeInfo?.Status?.toLowerCase() !== "not-yet-aired"; return ( <> - {pageTitle} - - - - - - - - + {seoData.title} + + + + + + + - - - - - - - + + + + + +
+
- {/* Main Content */} -
{/* Mobile Layout */}
- {/* Poster Section */}
-
- {`${safeTitle} +
+ {seoData.safeTitle} {animeInfo.adultContent && ( -
- 18+ -
+
18+
)}
- {/* Basic Info Section */}
- {/* Title */}
-

- {safeTitle} -

+

{seoData.safeTitle}

{language === "EN" && japanese_title && ( -

JP Title: {japanese_title}

+

JP: {japanese_title}

)}
- - {/* Tags */} -
- {tags.map(({ condition, icon, text }, index) => - condition && ( - - ) - )} -
- - {/* Overview - Limited for mobile */} - {info?.Overview && ( -
- {info.Overview.length > 150 ? ( - <> - {isFull ? ( - info.Overview - ) : ( -
{info.Overview}
- )} - - - ) : ( - info.Overview - )} -
- )} + + setIsFull(!isFull)} isMobile />
- {/* Watch Button - Full Width on Mobile */}
- {animeInfo?.animeInfo?.Status?.toLowerCase() !== "not-yet-aired" ? ( - - + {isAiring ? ( + + Watch Now ) : ( -
- Not released +
+ Not yet released
)}
- {/* Details Section - Full Width on Mobile */} -
-
- {[ - { label: "Japanese", value: info?.Japanese }, - { label: "Synonyms", value: info?.Synonyms }, - { label: "Aired", value: info?.Aired }, - { label: "Premiered", value: info?.Premiered }, - { label: "Duration", value: info?.Duration }, - { label: "Status", value: info?.Status }, - { label: "MAL Score", value: info?.["MAL Score"] }, - ].map((item, index) => ( - - ))} -
- - {/* Genres */} - {info?.Genres && ( -
-

Genres

-
- {info.Genres.map((genre, index) => ( - - {genre} - - ))} -
-
- )} - - {/* Studios & Producers */} -
- {[ - { label: "Studios", value: info?.Studios }, - { label: "Producers", value: info?.Producers }, - ].map((item, index) => ( - - ))} -
+
+
- {/* Desktop Layout - Existing Code */} + {/* Desktop Layout */}
- {/* Poster Section */}
-
- {`${safeTitle} +
+ {seoData.safeTitle} {animeInfo.adultContent && ( -
- 18+ -
+
18+
)}
- {/* Info Section */}
- {/* Title */}
-

- {safeTitle} -

+

{seoData.safeTitle}

{language === "EN" && japanese_title && (

JP Title: {japanese_title}

)}
- {/* Tags */} -
- {tags.map(({ condition, icon, text }, index) => - condition && ( - - ) - )} -
+ + setIsFull(!isFull)} /> - {/* Overview */} - {info?.Overview && ( -
- {info.Overview.length > 270 ? ( - <> - {isFull - ? info.Overview - : `${info.Overview.slice(0, 270)}...`} - - - ) : ( - info.Overview - )} -
- )} - - {/* Watch Button */} - {animeInfo?.animeInfo?.Status?.toLowerCase() !== "not-yet-aired" ? ( - - + {isAiring ? ( + + Watch Now ) : ( -
- Not released +
+ Not yet released
)} - {/* Details Section */} -
-
- {[ - { label: "Japanese", value: info?.Japanese }, - { label: "Synonyms", value: info?.Synonyms }, - { label: "Aired", value: info?.Aired }, - { label: "Premiered", value: info?.Premiered }, - { label: "Duration", value: info?.Duration }, - { label: "Status", value: info?.Status }, - { label: "MAL Score", value: info?.["MAL Score"] }, - ].map((item, index) => ( - - ))} -
- - {/* Genres */} - {info?.Genres && ( -
-

Genres

-
- {info.Genres.map((genre, index) => ( - - {genre} - - ))} -
-
- )} - - {/* Studios & Producers */} -
- {[ - { label: "Studios", value: info?.Studios }, - { label: "Producers", value: info?.Producers }, - ].map((item, index) => ( - - ))} -
-
+
- {/* Seasons Section */} - {seasons?.length > 0 && ( -
-

More Seasons

-
- {seasons.map((season, index) => ( - - {season.season} - {/* Dots Pattern Overlay */} -
')`, - backgroundSize: '3px 3px' - }} - /> - {/* Dark Gradient Overlay */} -
- {/* Title Container */} -
-

- {season.season} -

-
- - ))} + {/* Sections */} +
+ {seasons?.length > 0 && ( +
+

More Seasons

+
+ {seasons.map((season, index) => ( + + {season.season} +
')`, + backgroundSize: '3px 3px' + }} + /> +
+
+

{season.season}

+
+ + ))} +
-
- )} + )} - {/* Voice Actors Section */} - {animeInfo?.charactersVoiceActors.length > 0 && ( -
- -
- )} + {animeInfo?.charactersVoiceActors?.length > 0 && ( +
+ +
+ )} - {/* Recommendations Section */} - {animeInfo.recommended_data.length > 0 && ( -
- -
- )} + {animeInfo?.recommended_data?.length > 0 && ( +
+ +
+ )} +
); diff --git a/src/pages/watch/Watch.jsx b/src/pages/watch/Watch.jsx index 3f637ae..92ffc59 100644 --- a/src/pages/watch/Watch.jsx +++ b/src/pages/watch/Watch.jsx @@ -1,8 +1,7 @@ /* eslint-disable react/prop-types */ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, useMemo, useCallback } from "react"; import { useLocation, useParams, Link, useNavigate } from "react-router-dom"; import { useLanguage } from "@/src/context/LanguageContext"; -import { useHomeInfo } from "@/src/context/HomeInfoContext"; import { useWatch } from "@/src/hooks/useWatch"; import BouncingLoader from "@/src/components/ui/bouncingloader/Bouncingloader"; import IframePlayer from "@/src/components/player/IframePlayer"; @@ -12,6 +11,7 @@ import Sidecard from "@/src/components/sidecard/Sidecard"; import { faClosedCaptioning, faMicrophone, + faPlay, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Servers from "@/src/components/servers/Servers"; @@ -22,6 +22,7 @@ import useWatchControl from "@/src/hooks/useWatchControl"; import Player from "@/src/components/player/Player"; import getSafeTitle from "@/src/utils/getSafetitle"; import { Helmet } from 'react-helmet-async'; +import InfoTag from "@/src/components/ui/InfoTag/InfoTag"; import { generateDescription, generateKeywords, @@ -39,13 +40,11 @@ export default function Watch() { const { id: animeId } = useParams(); const queryParams = new URLSearchParams(location.search); let initialEpisodeId = queryParams.get("ep"); - const [tags, setTags] = useState([]); const { language } = useLanguage(); - const { homeInfo } = useHomeInfo(); const isFirstSet = useRef(true); const [showNextEpisodeSchedule, setShowNextEpisodeSchedule] = useState(true); + const { - // error, buffering, streamInfo, streamUrl, @@ -61,7 +60,6 @@ export default function Watch() { thumbnail, setIsFullOverview, activeEpisodeNum, - seasons, episodeId, setEpisodeId, activeServerId, @@ -71,8 +69,10 @@ export default function Watch() { activeServerType, setActiveServerType, activeServerName, - setActiveServerName + setActiveServerName, + seasons } = useWatch(animeId, initialEpisodeId); + const { autoPlay, setAutoPlay, @@ -81,511 +81,280 @@ export default function Watch() { autoNext, setAutoNext, } = useWatchControl(); - const playerRef = useRef(null); + const videoContainerRef = useRef(null); const controlsRef = useRef(null); const episodesRef = useRef(null); + // Sync URL with episodeId useEffect(() => { - if (!episodes || episodes.length === 0) return; + if (!episodes?.length) return; - const isValidEpisode = episodes.some(ep => { - const epNumber = ep.id.split('ep=')[1]; - return epNumber === episodeId; - }); + const currentEpNum = episodeId; + const isValidEpisode = episodes.some(ep => ep.id.split('ep=')[1] === currentEpNum); - // If missing or invalid episodeId, fallback to first - if (!episodeId || !isValidEpisode) { + if (!currentEpNum || !isValidEpisode) { const fallbackId = episodes[0].id.match(/ep=(\d+)/)?.[1]; - if (fallbackId && fallbackId !== episodeId) { - setEpisodeId(fallbackId); - } + if (fallbackId && fallbackId !== currentEpNum) setEpisodeId(fallbackId); return; } - const newUrl = `/watch/${animeId}?ep=${episodeId}`; + const newUrl = `/watch/${animeId}?ep=${currentEpNum}`; if (isFirstSet.current) { navigate(newUrl, { replace: true }); isFirstSet.current = false; } else { navigate(newUrl); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [episodeId, animeId, navigate, episodes]); - - - - // ... inside Watch component ... - // Update document title + }, [episodeId, animeId, navigate, episodes, setEpisodeId]); // Redirect if no episodes useEffect(() => { - if (totalEpisodes !== null && totalEpisodes === 0) { - navigate(`/${animeId}`); + if (totalEpisodes === 0) navigate(`/${animeId}`); + }, [animeId, totalEpisodes, navigate]); + + // Height adjustment logic + const adjustHeight = useCallback(() => { + if (window.innerWidth > 1200) { + if (videoContainerRef.current && controlsRef.current && episodesRef.current) { + const totalHeight = videoContainerRef.current.offsetHeight + controlsRef.current.offsetHeight; + episodesRef.current.style.height = `${totalHeight}px`; + } + } else if (episodesRef.current) { + episodesRef.current.style.height = 'auto'; } - }, [streamInfo, episodeId, animeId, totalEpisodes, navigate]); + }, []); useEffect(() => { - // Function to adjust the height of episodes list to match only video + controls - const adjustHeight = () => { - if (window.innerWidth > 1200) { - if (videoContainerRef.current && controlsRef.current && episodesRef.current) { - // Calculate combined height of video container and controls - const videoHeight = videoContainerRef.current.offsetHeight; - const controlsHeight = controlsRef.current.offsetHeight; - const totalHeight = videoHeight + controlsHeight; + const resizeObserver = new ResizeObserver(adjustHeight); - // Apply the combined height to episodes container - episodesRef.current.style.height = `${totalHeight}px`; - } - } else { - if (episodesRef.current) { - episodesRef.current.style.height = 'auto'; - } - } - }; + if (videoContainerRef.current) resizeObserver.observe(videoContainerRef.current); + if (controlsRef.current) resizeObserver.observe(controlsRef.current); - // Initial adjustment with delay to ensure player is fully rendered - const initialTimer = setTimeout(() => { - adjustHeight(); - }, 500); - - // Set up resize listener window.addEventListener('resize', adjustHeight); + adjustHeight(); - // Create MutationObserver to monitor player changes - const observer = new MutationObserver(() => { - setTimeout(adjustHeight, 100); - }); - - // Start observing both video container and controls - if (videoContainerRef.current) { - observer.observe(videoContainerRef.current, { - attributes: true, - childList: true, - subtree: true - }); - } - - if (controlsRef.current) { - observer.observe(controlsRef.current, { - attributes: true, - childList: true, - subtree: true - }); - } - - // Set up additional interval for continuous adjustments - const intervalId = setInterval(adjustHeight, 1000); - - // Clean up return () => { - clearTimeout(initialTimer); - clearInterval(intervalId); - observer.disconnect(); + resizeObserver.disconnect(); window.removeEventListener('resize', adjustHeight); }; - }, [buffering, activeServerType, activeServerName, episodeId, streamUrl, episodes]); + }, [adjustHeight, buffering, animeInfoLoading]); - function Tag({ bgColor, index, icon, text }) { - return ( -
- {icon && } -

{text}

-
- ); - } + const seoData = useMemo(() => { + if (!animeInfo) return null; + const safeT = getSafeTitle(animeInfo.title, language, animeInfo.japanese_title); + return { + safeTitle: safeT, + pageTitle: optimizeTitle(`Watch ${safeT} Episode ${activeEpisodeNum} Sub Dub Online Free`), + pageDescription: generateDescription(`Stream ${safeT} Episode ${activeEpisodeNum} in HD with English Sub and Dub. ${animeInfo.animeInfo?.Overview}`), + pageKeywords: `${generateKeywords(animeInfo)}, episode ${activeEpisodeNum}`, + canonicalUrl: generateCanonicalUrl(`/watch/${animeId}?ep=${episodeId}`), + ogImage: generateOGImage(animeInfo.poster), + structured: generateAnimeStructuredData(animeInfo, { number: activeEpisodeNum, id: episodeId }), + videoStructured: generateVideoStructuredData(animeInfo, { number: activeEpisodeNum, id: episodeId }, streamUrl), + breadcrumb: generateBreadcrumbStructuredData([ + { name: 'Home', url: '/' }, + { name: animeInfo.title, url: `/${animeId}` }, + { name: `Episode ${activeEpisodeNum}`, url: `/watch/${animeId}?ep=${episodeId}` } + ]) + }; + }, [animeId, animeInfo, activeEpisodeNum, episodeId, language, streamUrl]); - useEffect(() => { - setTags([ - { - condition: animeInfo?.animeInfo?.tvInfo?.rating, - bgColor: "#ffffff", - text: animeInfo?.animeInfo?.tvInfo?.rating, - }, - { - condition: animeInfo?.animeInfo?.tvInfo?.quality, - bgColor: "#FFBADE", - text: animeInfo?.animeInfo?.tvInfo?.quality, - }, - { - condition: animeInfo?.animeInfo?.tvInfo?.sub, - icon: faClosedCaptioning, - bgColor: "#B0E3AF", - text: animeInfo?.animeInfo?.tvInfo?.sub, - }, - { - condition: animeInfo?.animeInfo?.tvInfo?.dub, - icon: faMicrophone, - bgColor: "#B9E7FF", - text: animeInfo?.animeInfo?.tvInfo?.dub, - }, - ]); - }, [animeId, animeInfo]); + const tags = useMemo(() => { + const info = animeInfo?.animeInfo?.tvInfo; + if (!info) return []; + return [ + { condition: info.rating, text: info.rating, bgColor: "#ffffff" }, + { condition: info.quality, text: info.quality, bgColor: "#FFBADE" }, + { condition: info.sub, text: info.sub, icon: faClosedCaptioning, bgColor: "#B0E3AF" }, + { condition: info.dub, text: info.dub, icon: faMicrophone, bgColor: "#B9E7FF" }, + ]; + }, [animeInfo]); - const safeTitle = animeInfo ? getSafeTitle(animeInfo.title, language, animeInfo.japanese_title) : ''; - const pageTitle = animeInfo ? optimizeTitle(`Watch ${safeTitle} Episode ${activeEpisodeNum} Sub Dub Online Free`) : `${website_name} | Free anime streaming platform`; - const pageDescription = animeInfo ? generateDescription(`Stream ${safeTitle} Episode ${activeEpisodeNum} in HD with English Sub and Dub. ${animeInfo.animeInfo?.Overview}`) : ''; - const pageKeywords = animeInfo ? generateKeywords(animeInfo) + `, episode ${activeEpisodeNum}` : ''; - const canonicalUrl = generateCanonicalUrl(`/watch/${animeId}?ep=${episodeId}`); - const ogImage = animeInfo ? generateOGImage(animeInfo.poster) : ''; - - const animeStructuredData = animeInfo ? generateAnimeStructuredData(animeInfo, { number: activeEpisodeNum, id: episodeId }) : null; - const videoStructuredData = animeInfo ? generateVideoStructuredData(animeInfo, { number: activeEpisodeNum, id: episodeId }, streamUrl) : null; - const breadcrumbData = animeInfo ? generateBreadcrumbStructuredData([ - { name: 'Home', url: '/' }, - { name: animeInfo.title, url: `/${animeId}` }, - { name: `Episode ${activeEpisodeNum}`, url: `/watch/${animeId}?ep=${episodeId}` } - ]) : null; return ( <> - - {pageTitle} - - - + {seoData && ( + + {seoData.pageTitle} + + + + + + + + + + + + + + )} - - - - - - - - - - - - {animeStructuredData && ( - - )} - {videoStructuredData && ( - - )} - {breadcrumbData && ( - - )} -
-
-
- {/* Left Column - Player, Controls, Servers */} +
+
+ + {/* Left Column */}
-
- {/* Video Container */} +
- {!buffering ? (["hd-1", "hd-4"].includes(activeServerName.toLowerCase()) ? - setEpisodeId(id)} - autoNext={autoNext} - /> : setEpisodeId(id)} - animeInfo={animeInfo} - episodeNum={activeEpisodeNum} - streamInfo={streamInfo} - /> + {!buffering ? ( + ["hd-1", "hd-4"].includes(activeServerName.toLowerCase()) ? ( + + ) : ( + + ) ) : (
)} -

- {!buffering && !activeServerType ? ( - servers ? ( - <> - Probably this server is down, try other servers -
- Either reload or try again after sometime - - ) : ( - <> - Probably streaming server is down -
- Either reload or try again after sometime - - ) - ) : null} -

+ {!buffering && !activeServerType && ( +
+

+ Streaming server seems to be down. Please try another server or reload the page. +

+
+ )}
- {/* Controls Section */}
{!buffering && (
setEpisodeId(id)} + autoPlay={autoPlay} setAutoPlay={setAutoPlay} + autoSkipIntro={autoSkipIntro} setAutoSkipIntro={setAutoSkipIntro} + autoNext={autoNext} setAutoNext={setAutoNext} + episodes={episodes} totalEpisodes={totalEpisodes} + episodeId={episodeId} onButtonClick={setEpisodeId} />
)} - {/* Title and Server Selection */}
-
- -
+
- {/* Next Episode Schedule */} {nextEpisodeSchedule?.nextEpisodeSchedule && showNextEpisodeSchedule && (
-
+
- 🚀 -
- Next episode estimated at - - {new Date( - new Date(nextEpisodeSchedule.nextEpisodeSchedule).getTime() - - new Date().getTimezoneOffset() * 60000 - ).toLocaleDateString("en-GB", { - day: "2-digit", - month: "2-digit", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: true, + 🚀 +
+ Next episode around: + + {new Date(nextEpisodeSchedule.nextEpisodeSchedule).toLocaleString("en-GB", { + day: "2-digit", month: "2-digit", year: "numeric", + hour: "2-digit", minute: "2-digit", hour12: true })}
- +
)}
- {/* Mobile-only Seasons Section */} - {seasons?.length > 0 && ( -
-

More Seasons

-
- {seasons.map((season, index) => ( - - {season.season} - {/* Dots Pattern Overlay */} -
')`, - backgroundSize: '3px 3px' - }} - /> - {/* Dark Gradient Overlay */} -
- {/* Title Container */} -
-

- {season.season} -

-
- - ))} + {/* Info Section */} +
+
+
+ {animeInfo ? ( + {seoData?.safeTitle} + ) : }
-
- )} - - {/* Mobile-only Episodes Section */} -
-
- {!episodes ? ( -
- -
- ) : ( - setEpisodeId(id)} - totalEpisodes={totalEpisodes} - /> - )} -
-
- - {/* Anime Info Section */} -
-
- {animeInfo && animeInfo?.poster ? ( - - ) : ( - - )} -
- {animeInfo && animeInfo?.title ? ( - -

- {getSafeTitle(animeInfo.title, language, animeInfo.japanese_title)} +
+ {animeInfo ? ( + +

+ {seoData?.safeTitle}

-
+
View Details - - - +
- ) : ( - - )} -
- {animeInfo ? ( - tags.map( - ({ condition, icon, text }, index) => - condition && ( - - {icon && } - {text} - - ) - ) - ) : ( - - )} + ) : } + +
+ {tags.map((tag, idx) => tag.condition && ( + + ))}
+ {animeInfo?.animeInfo?.Overview && ( -

- {animeInfo?.animeInfo?.Overview.length > 270 ? ( +

+ {animeInfo.animeInfo.Overview.length > 270 ? ( <> - {isFullOverview - ? animeInfo?.animeInfo?.Overview - : `${animeInfo?.animeInfo?.Overview.slice(0, 270)}...`} - - ) : ( - animeInfo?.animeInfo?.Overview - )} -

+ ) : animeInfo.animeInfo.Overview} +
)}
- {/* Desktop-only Seasons Section */} + {/* Seasons (Mobile only) */} {seasons?.length > 0 && ( -
-

More Seasons

-
- {seasons.map((season, index) => ( +
+

More Seasons

+
+ {seasons.map((season, idx) => ( - {season.season} - {/* Dots Pattern Overlay */} -
')`, - backgroundSize: '3px 3px' - }} - /> - {/* Dark Gradient Overlay */} -
- {/* Title Container */} -
-

- {season.season} -

+ {season.season} +
+
+

{season.season}

))} @@ -594,48 +363,34 @@ export default function Watch() { )}
- {/* Right Column - Episodes and Related (Desktop Only) */} -
- {/* Episodes Section */} -
+ {/* Right Column (Desktop Only) */} +
+
{!episodes ? ( -
- -
+
) : ( setEpisodeId(id)} + onEpisodeClick={setEpisodeId} totalEpisodes={totalEpisodes} /> )}
- {/* Related Anime Section */} - {animeInfoLoading ? ( -
- -
- ) : animeInfo?.related_data?.length > 0 && ( -
-

Related Anime

- + {!animeInfoLoading && animeInfo?.related_data?.length > 0 && ( +
+

Related Anime

+
)}
- {/* Mobile-only Related Section */} + {/* Related Anime (Mobile only) */} {!animeInfoLoading && animeInfo?.related_data?.length > 0 && ( -
-

Related Anime

- +
+

Related Anime

+
)}