aboutsummaryrefslogtreecommitdiff
path: root/pages/anime
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-04-11 23:23:29 +0700
committerFactiven <[email protected]>2023-04-11 23:23:29 +0700
commit1fcdd9f7d859b925bf92265f441655d5522e351c (patch)
tree86391522f6fcc70d105f7e796a9f91d132ee4a29 /pages/anime
parentInitial commit (diff)
downloadmoopa-1fcdd9f7d859b925bf92265f441655d5522e351c.tar.xz
moopa-1fcdd9f7d859b925bf92265f441655d5522e351c.zip
initial commit
Diffstat (limited to 'pages/anime')
-rw-r--r--pages/anime/[...id].js623
-rw-r--r--pages/anime/watch/[...info].js323
2 files changed, 946 insertions, 0 deletions
diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js
new file mode 100644
index 0000000..d42a394
--- /dev/null
+++ b/pages/anime/[...id].js
@@ -0,0 +1,623 @@
+import React, { useEffect, useState } from "react";
+import { AnimatePresence, motion as m } from "framer-motion";
+import { META } from "@consumet/extensions";
+
+import Link from "next/link";
+import Layout from "../../components/layout";
+import Head from "next/head";
+
+import { closestMatch } from "closest-match";
+import Content from "../../components/hero/content";
+import Image from "next/image";
+
+export default function Himitsu({
+ info,
+ slicedDesc,
+ color,
+ episodeList,
+ episode1,
+ judul,
+ subIndo,
+ epIndo,
+}) {
+ const [isLoading, setIsloading] = useState(false);
+ const [showText, setShowtext] = useState(false);
+ const [title, setTitle] = useState(info.title.english || info.title.romaji);
+ const [load, setLoad] = useState(true);
+ const [Lang, setLang] = useState(true);
+ const [showAll, setShowAll] = useState(false);
+
+ const [lastPlayed, setLastPlayed] = useState(null);
+ const episode = episodeList;
+ const epi1 = episode1;
+
+ const maxItems = 3;
+
+ function handleEnLang() {
+ setLang(true);
+ }
+
+ function handleIdLang() {
+ setLang(false);
+ }
+
+ // const { ref } = useParallax({ speed: 10 });
+
+ useEffect(() => {
+ const playedStr = JSON.parse(localStorage.getItem("lastPlayed"));
+ setLastPlayed(
+ playedStr?.filter((item) => item.title === info.title.romaji)[0]?.data
+ );
+ function getBrightness(color) {
+ const rgb = color.match(/\d+/g);
+ return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000;
+ }
+
+ // set the text color based on the background color
+ function setTextColor(element) {
+ const backgroundColor = getComputedStyle(element).backgroundColor;
+ const brightness = getBrightness(backgroundColor);
+ if (brightness < 128) {
+ element.style.color = "#fff"; // white
+ } else {
+ element.style.color = "#000"; // black
+ }
+ }
+
+ const elements = document.querySelectorAll(".dynamic-text");
+ elements.forEach((element) => {
+ setTextColor(element);
+ });
+ }, [color]);
+
+ const handleStore = (props) => {
+ let existingData = JSON.parse(localStorage.getItem("recentWatch"));
+ if (!Array.isArray(existingData)) {
+ existingData = [];
+ }
+ const index = existingData.findIndex((item) => item.title === props.title);
+ if (index !== -1) {
+ existingData.splice(index, 1);
+ }
+ const updatedData = [props, ...existingData];
+ localStorage.setItem("recentWatch", JSON.stringify(updatedData));
+ };
+
+ if (!info) {
+ return;
+ }
+
+ let episodeIndo = null;
+ if (epIndo < 17) {
+ episodeIndo = episode.slice(0, epIndo);
+ } else {
+ episodeIndo = episode;
+ }
+
+ // console.log({ NEXT: subIndo });
+
+ // console.log(episodeIndo);
+
+ // console.log(lastPlayed);
+
+ function handleLoad() {
+ setLoad(false);
+ }
+ return (
+ <>
+ <Head>
+ <title>{info.title?.english || info.title.romaji}</title>
+ <meta name="detail" content="Detail about the Anime" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="icon" href="/c.svg" />
+ </Head>
+
+ <Layout navTop="text-white bg-[#121212] md:pt-0 md:px-0 bg-slate bg-opacity-40">
+ <div className="text static flex w-screen flex-col justify-center pt-nav pb-10">
+ <div className="pointer-events-none absolute top-0 left-0">
+ <div className="brightness-90 bg-gradient-to-t from-[#121212] to-transparent">
+ <img
+ // ref={ref}
+ src={info.cover || info.image}
+ className="h-[300px] w-screen object-cover brightness-75 mix-blend-darken"
+ />
+ <div className="z-10 h-full drop-shadow-2xl bg-[#121212]" />
+ </div>
+ </div>
+ {isLoading ? (
+ <p>Loading cuy sabar...</p>
+ ) : info ? (
+ <div className="flex flex-col items-center gap-10">
+ <div className="flex w-screen flex-col gap-10 md:w-[70%]">
+ <div className="z-40 flex flex-col gap-10 px-5 pt-[8rem] md:flex-row lg:mt-[5rem] lg:px-0">
+ <div className="flex gap-10 md:h-[250px] md:w-52">
+ <div className="flex h-[200px] w-52 bg-[#dadada50] md:h-[250px] md:w-full">
+ {info.image && (
+ <>
+ <div
+ style={{
+ backgroundImage: `url(${info.image})`,
+ height: "100%",
+ width: "100%",
+ backgroundSize: "cover",
+ backgroundPosition: "center",
+ }}
+ // src={info.image}
+ className="h-[200px] w-[200px] md:h-[250px] bg-white shadow-md"
+ />
+ </>
+ )}
+ </div>
+
+ {/* MOBILE */}
+ <div className="flex w-full flex-col gap-5 lg:hidden ">
+ <h1 className="shrink-0 text-2xl font-semibold">
+ {judul}
+ </h1>
+ <div className="flex w-[90%] flex-col gap-1">
+ <div className="flex gap-2">
+ <h1>Rate:</h1>
+ <p className="font-bold">{info.rating}%</p>
+ </div>
+
+ <div className="flex w-[200px] gap-2">
+ <h1>Format:</h1>
+ <p>{info.type}</p>
+ </div>
+
+ <div className="flex gap-2">
+ <h1>Status:</h1>
+ <p>{info.status}</p>
+ </div>
+ </div>
+ <div className="flex">
+ {epi1 && epi1[0] ? (
+ <Link
+ href={`/anime/watch/${epi1[0].id}/${info.id}`}
+ onClick={() =>
+ handleStore({
+ title:
+ info.title?.english ||
+ info.title.romaji ||
+ info.title.native,
+ description: info.description,
+ image: info.image,
+ id: info.id,
+ })
+ }
+ >
+ <h1 className="flex cursor-pointer items-center gap-2 rounded-[20px] bg-[#ff9537] px-4 py-2 font-bold text-[#ffffff]">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="13"
+ height="12"
+ fill="none"
+ viewBox="0 0 250 289"
+ >
+ <path
+ fill="#fff"
+ d="M249.734 144.5l-249 143.761V.741l249 143.759z"
+ ></path>
+ </svg>{" "}
+ WATCH
+ </h1>
+ </Link>
+ ) : (
+ <h1 className="pointer-events-none flex items-center gap-2 rounded-[20px] bg-[#ff94378f] px-4 py-2 font-bold text-[#ffffffa5]">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="13"
+ height="12"
+ className="fill-[#ffffff8d]"
+ viewBox="0 0 250 289"
+ >
+ <path d="M249.734 144.5l-249 143.761V.741l249 143.759z"></path>
+ </svg>{" "}
+ WATCH
+ </h1>
+ )}
+ </div>
+ </div>
+ </div>
+
+ {/* PC */}
+ <div className="w-full flex-col gap-5 md:flex">
+ <div className="hidden flex-col gap-5 lg:flex">
+ <h1 className="text-4xl font-bold">
+ {info.title?.english ||
+ info.title.romaji ||
+ info.title.native}
+ </h1>
+ <div className="flex gap-6">
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {episode && episode.length} Episodes
+ </div>
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {info.releaseDate}
+ </div>
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {info.rating}%
+ </div>
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {info.type}
+ </div>
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {info.status}
+ </div>
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ Sub | {subIndo === null ? "EN" : "EN/ID"}
+ </div>
+ </div>
+ </div>
+ <div
+ className={`hidden h-[140px] transition-all duration-300 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-md hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] lg:block`}
+ >
+ <p
+ dangerouslySetInnerHTML={{ __html: info.description }}
+ className="mr-5"
+ />
+ </div>
+ <div className="lg:hidden">
+ <div
+ dangerouslySetInnerHTML={{
+ __html: showText ? info.description : slicedDesc,
+ }}
+ ></div>
+ <button
+ onClick={() => setShowtext(!showText)}
+ className="font-rama font-bold text-white"
+ >
+ {showText ? " Show Less" : " Show More"}
+ </button>
+ </div>
+ </div>
+ </div>
+
+ <div className="">
+ <div className="flex gap-5 items-center">
+ <div className="p-3 lg:p-0 text-3xl font-bold">
+ Relations
+ </div>
+ {info.relations.length > maxItems && (
+ <div
+ className="cursor-pointer"
+ onClick={() => setShowAll(!showAll)}
+ >
+ {showAll ? "show less" : "show more"}
+ </div>
+ )}
+ </div>
+ <div
+ className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 px-3 lg:px-4 pt-10 rounded-xl`}
+ >
+ {info.relations &&
+ info.relations
+ .slice(0, showAll ? info.relations.length : maxItems)
+ .map((relation, index) => {
+ return (
+ <Link
+ key={relation.id}
+ href={
+ relation.type === "TV" ||
+ relation.type === "OVA" ||
+ relation.type === "MOVIE" ||
+ relation.type === "SPECIAL" ||
+ relation.type === "ONA"
+ ? `/anime/${relation.id}`
+ : `/manga/detail/id?aniId=${
+ relation.id
+ }&aniTitle=${encodeURIComponent(
+ info.title?.english ||
+ info.title.romaji ||
+ info.title.native
+ )}`
+ }
+ className={`hover:scale-[1.02] scale-100 transition-transform duration-200 ease-out w-full ${
+ relation.type === "MUSIC"
+ ? "pointer-events-none"
+ : ""
+ }`}
+ >
+ <div
+ key={relation.id}
+ initial={{ opacity: 0, y: 50 }}
+ animate={{ opacity: 1, y: 0 }}
+ exit={{
+ opacity: 0,
+ y: -50,
+ transition: { duration: 0.5 },
+ }}
+ transition={{
+ duration: 0.8,
+ delay: index * 0.1,
+ }}
+ className="w-full shrink h-[126px] bg-secondary flex rounded-md"
+ >
+ <div className="min-w-[20%] bg-image rounded-l-md shrink-0">
+ <img
+ src={relation.image}
+ alt={relation.id}
+ className="object-cover h-full w-full shrink-0 rounded-l-md"
+ />
+ </div>
+ <div className="min-w-[80%] h-full grid px-3 items-center">
+ <div className="text-action font-outfit font-bold">
+ {relation.relationType}
+ </div>
+ <div className="font-outfit font-thin italic line-clamp-2">
+ {relation.title.romaji}
+ </div>
+ <div className={``}>{relation.type}</div>
+ </div>
+ </div>
+ </Link>
+ );
+ })}
+ </div>
+ </div>
+
+ <div className="z-20 flex flex-col gap-10 p-3 lg:p-0">
+ <div className="flex items-center gap-10">
+ <h1 className="text-3xl font-bold">Episodes</h1>
+ <div className="flex items-center rounded-md">
+ <button
+ onClick={handleEnLang}
+ className={
+ Lang
+ ? `w-16 p-2 rounded-l-md bg-[#212121]`
+ : `w-16 p-2 rounded-l-md bg-[#171717] text-[#404040]`
+ }
+ >
+ EN
+ </button>
+ <div className="w-[1px] bg-white h-4" />
+ <button
+ onClick={handleIdLang}
+ className={
+ subIndo === null
+ ? `w-16 p-2 rounded-r-md bg-[#171717] text-[#404040] pointer-events-none`
+ : Lang
+ ? `w-16 p-2 rounded-r-md bg-[#171717] text-[#404040]`
+ : `w-16 p-2 rounded-r-md bg-[#212121]`
+ }
+ >
+ ID
+ </button>
+ </div>
+ </div>
+ <div className="flex h-[640px] flex-col gap-5 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]">
+ {episode && Lang ? (
+ episode.map((episode, index) => {
+ const item = lastPlayed?.find(
+ (item) => item.id === episode.id
+ );
+ // console.log(item);
+ return (
+ <div key={index} className="flex flex-col gap-3">
+ <Link
+ onClick={() =>
+ handleStore({
+ title:
+ info.title?.english ||
+ info.title.romaji ||
+ info.title.native,
+ description: info.description,
+ image: info.image,
+ id: info.id,
+ })
+ }
+ href={`/anime/watch/${episode.id}/${info.id}/${
+ item ? `${item.time}` : ""
+ }`}
+ className={`text-start text-xl ${
+ item ? "text-[#414141]" : "text-white"
+ }`}
+ >
+ <p>Episode {episode.number}</p>
+ {episode.title && (
+ <p
+ className={`text-[14px] ${
+ item ? "text-[#414141]" : "text-[#b1b1b1]"
+ } italic`}
+ >
+ "{episode.title}"
+ </p>
+ )}
+ </Link>
+ <div className="h-[1px] bg-white" />
+ </div>
+ );
+ })
+ ) : subIndo === null ? (
+ <p>No Episodes Available</p>
+ ) : (
+ <>
+ <div className="flex h-[640px] flex-col gap-5 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]">
+ {episodeIndo.map((episode, index) => {
+ return (
+ <div key={index} className="flex flex-col gap-3">
+ <Link
+ onClick={() =>
+ handleStore({
+ title:
+ info.title?.english ||
+ info.title.romaji ||
+ info.title.native,
+ description: info.description,
+ image: info.image,
+ id: info.id,
+ })
+ }
+ href={`/anime/watch?title=${encodeURIComponent(
+ info.title?.romaji || info.title?.english
+ )}&id=${subIndo}&idInt=${info.id}&epi=${
+ episode.number
+ }&epiTitle=${encodeURIComponent(
+ episode.title
+ )}&te=${epIndo}&sub=id`}
+ className="text-start text-xl"
+ >
+ <p>Episode {episode.number}</p>
+ <p className="text-[14px] text-[#b1b1b1] italic">
+ "{episode.title}" (Sub Indonesia)
+ </p>
+ </Link>
+ <div className="h-[1px] bg-white" />
+ </div>
+ );
+ })}
+ </div>
+ </>
+ )}
+ </div>
+ </div>
+ </div>
+ <div className="w-screen md:w-[80%]">
+ <Content
+ ids="recommendAnime"
+ section="Recommendations"
+ data={info.recommendations}
+ />
+ </div>
+ </div>
+ ) : (
+ <div className="flex h-screen flex-col items-center justify-center gap-10 pb-52 ">
+ <h1 className="scale-150 font-roboto text-6xl text-red-400">
+ 404
+ </h1>
+ <p className="text-4xl font-semibold">{`> Woops.. I think we don't have that Anime :(`}</p>
+ <Link className="pt-10 text-2xl" href="/search">
+ Return to search
+ </Link>
+ </div>
+ )}
+ </div>
+ </Layout>
+ </>
+ );
+}
+
+export const getServerSideProps = async (context) => {
+ context.res.setHeader(
+ "Cache-Control",
+ "public, s-maxage=10, stale-while-revalidate=59"
+ );
+ const { id } = context.query;
+ if (!id) {
+ return {
+ notFound: true,
+ };
+ }
+
+ const provider = new META.Anilist();
+
+ const [info, episodes] = await Promise.all([
+ fetch(`https://api.moopa.my.id/meta/anilist/info/${id[0]}`).then((res) =>
+ res.json()
+ ),
+ provider.fetchEpisodesListById(id[0]),
+ ]);
+
+ let episodeList = episodes;
+ if (episodes.length === 0) {
+ const res = await fetch(
+ `https://api.moopa.my.id/anime/gogoanime/${
+ info.title.romaji || info.title.english
+ }`
+ );
+ const data = await res.json();
+ const match = closestMatch(
+ info.title.romaji,
+ data.results.map((item) => item.title)
+ );
+ const anime = data.results.filter((item) => item.title === match);
+ if (anime.length !== 0) {
+ const infos = await fetch(
+ `https://api.moopa.my.id/anime/gogoanime/info/${anime[0].id}`
+ ).then((res) => res.json());
+ episodeList = infos.episodes;
+ }
+ }
+
+ const ress = await fetch(
+ `https://ani-api-eight.vercel.app/kuramanime/search?query=${
+ info.title.romaji || info.title?.english
+ }`
+ );
+
+ const yes = await ress.json();
+
+ // Clannad Fixer
+ function convertToClannad(text) {
+ const regex = /(?<!\w)CLANNAD(?!\w)/g;
+ return text.replace(regex, "Clannad");
+ }
+
+ const fixedTitle = convertToClannad(info.title.romaji);
+
+ let epis = null;
+ let slug = null;
+
+ if (!yes.error) {
+ // let anime = yes.list.filter((item) => item.title.includes(fixedTitle));
+ let list = yes.list.map((item) => item.title);
+ const match = closestMatch(fixedTitle, list);
+
+ const anime = yes.list.filter((item) => item.title === match);
+
+ slug = anime[0]?.slug;
+ const inf = await fetch(
+ `https://ani-api-eight.vercel.app/kuramanime/anime/${slug}`
+ );
+
+ const dataInf = await inf.json();
+ epis = dataInf.episode;
+ }
+
+ const desc = info.description.slice(0, 150) + "...";
+ const color = { backgroundColor: `${info.color}` };
+ const epi1 = episodes.filter((epi) => epi.number === 1);
+ const title = info.title?.userPreferred || "No Title";
+
+ const MAX = 20;
+
+ const oriJ = info.title?.english || info.title.romaji || info.title.native;
+ const judul = oriJ.length > MAX ? `${oriJ.substring(0, MAX)}...` : oriJ;
+
+ return {
+ props: {
+ info: {
+ ...info,
+ title: {
+ ...info.title,
+ userPreferred: title,
+ },
+ },
+ slicedDesc: desc,
+ color,
+ episodeList,
+ episode1: epi1,
+ judul,
+ subIndo: slug,
+ epIndo: epis,
+ },
+ };
+};
diff --git a/pages/anime/watch/[...info].js b/pages/anime/watch/[...info].js
new file mode 100644
index 0000000..73e04f5
--- /dev/null
+++ b/pages/anime/watch/[...info].js
@@ -0,0 +1,323 @@
+import Layout from "../../../components/layout";
+// import { data } from "../../../lib/testData";
+// import { aniData } from "../../../lib/infoData";
+import Image from "next/image";
+import VideoPlayer from "../../../components/videoPlayer";
+import Link from "next/link";
+import { closestMatch } from "closest-match";
+import Head from "next/head";
+import { useEffect, useState } from "react";
+import Modal from "../../../components/modal";
+
+export default function Info({ info }) {
+ const title = info.aniData.title.romaji || info.aniData.title.english;
+ const data = info.aniData;
+ const fallback = info.epiFallback;
+
+ const [open, setOpen] = useState(false);
+
+ const playingTitle = data.episodes
+ .filter((item) => item.id == info.id)
+ .map((item) => item.title);
+
+ return (
+ <>
+ <Head>
+ <title>
+ {fallback ? data.title.romaji || data.title.english : playingTitle}
+ </title>
+ </Head>
+ <Modal open={open} onClose={() => setOpen(false)}>
+ <div className="bg-[#202020] rounded-lg w-[268px] text-center">
+ <div className="p-5 grid gap-2 justify-center place-items-center">
+ <h1 className="text-md font-extrabold font-karla">
+ Save this Anime to Your List
+ </h1>
+ <h1 className="text-sm font-karla font-extralight w-[205px]">
+ Are you sure you want to save this anime to your list?
+ </h1>
+ <div className="flex gap-12 items-center pt-3 justify-between">
+ <button className="p-2 font-karla font-extrabold text-sm bg-[#93FF3E] w-[84px] rounded-[10px] text-black shadow">
+ YES
+ </button>
+ <button
+ onClick={() => setOpen(false)}
+ className="p-2 font-karla font-extrabold text-sm bg-white w-[84px] rounded-[10px] text-black shadow-lg"
+ >
+ NO
+ </button>
+ </div>
+ </div>
+ </div>
+ </Modal>
+ <div className="min-h-screen flex flex-col lg:gap-0 gap-5 lg:flex-row lg:py-10 lg:px-10 justify-start w-screen">
+ <div className="w-screen lg:w-[67%]">
+ <div className="h-auto aspect-video z-20">
+ <VideoPlayer
+ key={info.id}
+ data={info.epiData}
+ seek={info.seek}
+ titles={title}
+ id={info.id}
+ />
+ </div>
+ <div>
+ <div className="">
+ {data.episodes.length > 0 ? (
+ data.episodes
+ .filter((items) => items.id == info.id)
+ .map((item) => (
+ <div key={item.id} className="p-3 grid gap-2">
+ <div className="text-xl font-outfit font-semibold line-clamp-2">
+ <Link
+ href={`/anime/${data.id}`}
+ className="inline hover:underline"
+ >
+ {item.title}
+ </Link>
+ </div>
+ <h4 className="text-sm font-karla font-light">
+ Episode {item.number}
+ </h4>
+ </div>
+ ))
+ ) : (
+ <>
+ {fallback
+ .filter((item) => item.id == info.id)
+ .map((item) => (
+ <div key={item.id} className="p-3 grid gap-2">
+ <div className="text-xl font-outfit font-semibold line-clamp-2">
+ <Link
+ href={`/anime/${data.id}`}
+ className="inline hover:underline"
+ >
+ {title}
+ </Link>
+ </div>
+ <h4 className="text-sm font-karla font-light">
+ Episode {item.number}
+ </h4>
+ </div>
+ ))}
+ </>
+ )}
+ </div>
+ <div className="h-[1px] bg-[#3b3b3b]" />
+ <div>
+ <div className="px-4 pt-7 pb-4 h-full flex">
+ <div className="aspect-[9/13] h-[240px]">
+ <Image
+ src={data.image}
+ alt="Anime Cover"
+ width={1000}
+ height={1000}
+ className="object-cover aspect-[9/13] h-[240px] rounded-md"
+ />
+ </div>
+ <div className="grid w-full px-5 gap-3 h-[240px]">
+ <div className="grid grid-cols-2 gap-1 items-center">
+ <h2 className="text-sm font-light font-roboto text-[#878787]">
+ Studios
+ </h2>
+ <div className="row-start-2">{data.studios}</div>
+ <div className="grid col-start-2 place-content-end relative">
+ <div className="" onClick={() => setOpen(true)}>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ className="w-8 h-8 hover:fill-white hover:cursor-pointer"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0111.186 0z"
+ />
+ </svg>
+ {/* <span className=" transition-all duration-300 absolute -top-12 bg-[#2c2c2c] font-karla p-2 rounded-l-lg rounded-tr-lg right-3 select-none">
+ Save to My List
+ </span> */}
+ </div>
+ </div>
+ </div>
+ <div className="grid gap-1 items-center">
+ <h2 className="text-sm font-light font-roboto text-[#878787]">
+ Status
+ </h2>
+ <div>{data.status}</div>
+ </div>
+ <div className="grid gap-1 items-center overflow-y-hidden">
+ <h2 className="text-sm font-light font-roboto text-[#878787]">
+ Titles
+ </h2>
+ <div className="grid grid-flow-dense grid-cols-2 gap-2 h-full w-full">
+ <div className="line-clamp-3">
+ {data.title.romaji || ""}
+ </div>
+ <div className="line-clamp-3">
+ {data.title.english || ""}
+ </div>
+ <div className="line-clamp-3">
+ {data.title.native || ""}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className="flex flex-wrap gap-3 px-4 pt-3 ">
+ {data.genres.map((item, index) => (
+ <div
+ key={index}
+ className="border border-action text-gray-100 py-1 px-2 rounded-md font-karla text-sm"
+ >
+ {item}
+ </div>
+ ))}
+ </div>
+ <div className={`bg-[#2a2a2a] rounded-md mt-3 mx-3`}>
+ <p
+ dangerouslySetInnerHTML={{ __html: data.description }}
+ className={`p-5 text-sm font-light font-roboto text-[#e4e4e4] `}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className="flex flex-col w-screen lg:w-[33%] ">
+ <h1 className="text-xl font-karla pl-4 pb-5 font-semibold">
+ Episodes
+ </h1>
+ <div className="grid gap-5 lg:px-5 px-2 py-2 scrollbar-thin scrollbar-thumb-[#313131] scrollbar-thumb-rounded-full">
+ {data.episodes.length > 0
+ ? data.episodes.map((item) => {
+ return (
+ <Link
+ href={`/anime/watch/${item.id}/${data.id}`}
+ key={item.id}
+ className={`bg-secondary flex w-full h-[110px] rounded-lg scale-100 transition-all duration-300 ease-out ${
+ item.id == info.id
+ ? "pointer-events-none ring-1 ring-action"
+ : "cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white"
+ }`}
+ >
+ <div className="w-[40%] h-full relative shrink-0">
+ <Image
+ src={item.image}
+ alt="image"
+ height={1000}
+ width={1000}
+ className={`object-cover rounded-lg h-[110px] shadow-[4px_0px_5px_0px_rgba(0,0,0,0.3)] ${
+ item.id == info.id
+ ? "brightness-[30%]"
+ : "brightness-75"
+ }`}
+ />
+ <span className="absolute bottom-2 left-2 font-karla font-light text-sm">
+ Episode {item.number}
+ </span>
+ {item.id == info.id && (
+ <div className="absolute top-11 left-[105px] scale-[1.5]">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ className="w-5 h-5"
+ >
+ <path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" />
+ </svg>
+ </div>
+ )}
+ </div>
+ <div
+ className={`w-[70%] h-full select-none p-4 flex flex-col gap-2 ${
+ item.id == info.id ? "text-[#7a7a7a]" : ""
+ }`}
+ >
+ <h1 className="font-karla font-bold italic line-clamp-1">
+ {item.title}
+ </h1>
+ <p className="line-clamp-2 text-xs italic font-outfit font-extralight">
+ {item.description}
+ </p>
+ </div>
+ </Link>
+ );
+ })
+ : fallback.map((item) => {
+ return (
+ <Link
+ href={`/anime/watch/${item.id}/${data.id}`}
+ key={item.id}
+ className={`bg-secondary flex-center w-full h-[50px] rounded-lg scale-100 transition-all duration-300 ease-out ${
+ item.id == info.id
+ ? "pointer-events-none ring-1 ring-action text-[#5d5d5d]"
+ : "cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white"
+ }`}
+ >
+ Episode {item.number}
+ </Link>
+ );
+ })}
+ </div>
+ </div>
+ </div>
+ </>
+ );
+}
+
+export async function getServerSideProps(context) {
+ const { info } = context.query;
+ if (!info) {
+ return {
+ notFound: true,
+ };
+ }
+
+ const id = info[0];
+ const aniId = info[1];
+ const seek = info[2] || 0;
+ let epiFallback = null;
+
+ const res = await fetch(`https://api.moopa.my.id/meta/anilist/watch/${id}`);
+ const epiData = await res.json();
+
+ const res2 = await fetch(
+ `https://api.moopa.my.id/meta/anilist/info/${aniId}`
+ );
+ const aniData = await res2.json();
+
+ if (aniData.episodes.length === 0) {
+ const res = await fetch(
+ `https://api.moopa.my.id/anime/gogoanime/${
+ aniData.title.romaji || aniData.title.english
+ }`
+ );
+ const data = await res.json();
+ const match = closestMatch(
+ aniData.title.romaji,
+ data.results.map((item) => item.title)
+ );
+ const anime = data.results.filter((item) => item.title === match);
+ if (anime.length !== 0) {
+ const infos = await fetch(
+ `https://api.moopa.my.id/anime/gogoanime/info/${anime[0].id}`
+ ).then((res) => res.json());
+ epiFallback = infos.episodes;
+ }
+ }
+
+ return {
+ props: {
+ info: {
+ id,
+ seek,
+ epiData,
+ aniData,
+ epiFallback,
+ },
+ },
+ };
+}