- {/* Desktop Title Section */}
-
-
{info.name}
- {info.jname && (
- {info.jname}
- )}
-
-
- {/* Desktop Quick Info */}
-
- {info.stats?.rating && (
-
-
-
{info.stats.rating}
-
- )}
- {moreInfo?.status && (
-
- {moreInfo.status}
-
- )}
- {info.stats?.type && (
-
- {info.stats.type}
-
- )}
- {info.stats?.episodes && (
-
- {info.stats.episodes.sub > 0 && `SUB ${info.stats.episodes.sub}`}
- {info.stats.episodes.dub > 0 && info.stats.episodes.sub > 0 && ' | '}
- {info.stats.episodes.dub > 0 && `DUB ${info.stats.episodes.dub}`}
-
- )}
- {info.stats?.quality && (
-
- {info.stats.quality}
-
- )}
- {info.stats?.duration && (
-
- {info.stats.duration}
-
- )}
-
-
- {/* Synopsis */}
-
-
Synopsis
-
-
- {info.description || 'No description available for this anime.'}
-
- {info.description && info.description.length > 100 && (
-
- )}
-
-
-
- {/* Watch Button */}
- {info.stats?.episodes && (info.stats.episodes.sub > 0 || info.stats.episodes.dub > 0) && (
-
-
-
-
Start Watching
-
+
+ {info.stats?.quality && (
+
+ {info.stats.quality}
)}
-
- {/* Promotional Videos */}
- {info.promotionalVideos && info.promotionalVideos.length > 0 && (
-
-
Promotional Videos
-
- {info.promotionalVideos.map((video, index) => (
-
setActiveVideo(video)}
+
+ {info.stats?.duration && (
+
+ {info.stats.duration}
+
+ )}
+
+
+ {/* Genres & Studios */}
+
+ {/* Genres */}
+ {moreInfo?.genres && moreInfo.genres.length > 0 && (
+
+
Genres
+
+ {moreInfo.genres.map((genre, index) => (
+
-
-
-
+ {genre}
+
))}
)}
- {/* Additional Info */}
-
- {/* Genres */}
- {moreInfo?.genres && moreInfo.genres.length > 0 && (
-
-
Genres
-
-
- {moreInfo.genres.map((genre, index) => (
-
- {genre}
-
- ))}
-
+ {/* Studios */}
+ {moreInfo?.studios && (
+
+
Studios
+
- )}
-
- {/* Studios */}
- {moreInfo?.studios && (
-
-
Studios
-
-
- {moreInfo.studios}
-
-
-
- )}
-
- {/* Aired Date */}
- {moreInfo?.aired && (
-
- )}
-
- {/* Character & Voice Actors */}
- {(info.characterVoiceActor?.length > 0 || info.charactersVoiceActors?.length > 0) && (
-
-
Characters & Voice Actors
-
- {(info.characterVoiceActor || info.charactersVoiceActors || []).map((item, index) => (
-
-
-
-
-
-
-
-
{item.character.name}
-
{item.character.cast}
-
-
-
-
-
-
-
-
-
-
{item.voiceActor.name}
-
{item.voiceActor.cast}
-
-
-
-
- ))}
-
-
- )}
-
+
+ )}
+ {/* Details Tabs - Synopsis, Characters, Videos */}
+
+ {/* Tab Navigation */}
+
+
+
+ {hasCharacters && (
+
+ )}
+
+ {hasVideos && (
+
+ )}
+
+
+ {/* Tab Content */}
+
+ {/* Synopsis Tab */}
+ {activeTab === 'synopsis' && (
+
+
+ {info.description || 'No description available for this anime.'}
+
+ {synopsisOverflows && (
+
+ )}
+
+ )}
+
+ {/* Characters Tab */}
+ {activeTab === 'characters' && hasCharacters && (
+
+ {(info.characterVoiceActor || info.charactersVoiceActors || []).map((item, index) => (
+
+
+ {/* Character Image - No padding */}
+
+
+
+
+ {/* Text content in the middle */}
+
+
+ {/* Character Name */}
+
+
{item.character.name}
+
{item.character.cast || 'Main'}
+
+
+ {/* Voice Actor Name */}
+
+
{item.voiceActor.name}
+
{item.voiceActor.cast || 'Japanese'}
+
+
+
+
+ {/* Voice Actor Image - No padding */}
+
+
+
+
+
+ ))}
+
+ )}
+
+ {/* Videos Tab */}
+ {activeTab === 'videos' && hasVideos && (
+
+ {info.promotionalVideos.map((video, index) => (
+
setActiveVideo(video)}
+ >
+
+
+
+ ))}
+
+ )}
+
+
+
{/* Seasons Section */}
- {renderSeasons()}
+ {seasons && seasons.length > 0 && (
+
+ )}
{/* Related Anime Section */}
- {renderAnimeCards(relatedAnime, 'Related Anime')}
+ {relatedAnime && relatedAnime.length > 0 && (
+
+ )}
{/* Recommendations Section */}
- {renderAnimeCards(recommendations, 'You May Also Like')}
-
- {/* Most Popular Section */}
- {renderAnimeCards(mostPopular, 'Most Popular')}
+ {recommendations && recommendations.length > 0 && (
+
+ )}
);
diff --git a/src/components/AnimeInfo.js b/src/components/AnimeInfo.js
deleted file mode 100644
index c1a0ba5..0000000
--- a/src/components/AnimeInfo.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import Image from 'next/image';
-
-export default function AnimeInfo({ anime }) {
- return (
-
-
- {/* Banner Image - You'll need to add bannerImage to your anime object */}
-
-
- {anime.bannerImage ? (
-
- ) : (
-
- )}
-
-
- {/* Content */}
-
-
- {/* Cover Image */}
-
-
- {/* Details */}
-
- {/* Title and Alternative Titles */}
-
-
- {anime.title}
-
- {anime.alternativeTitles && (
-
- {anime.alternativeTitles.join(' • ')}
-
- )}
-
-
- {/* Metadata Grid */}
-
-
-
Status
-
{anime.status}
-
-
-
Episodes
-
{anime.totalEpisodes}
-
-
-
Season
-
{anime.season} {anime.year}
-
-
-
Studio
-
{anime.studio}
-
-
-
- {/* Genres */}
- {anime.genres && (
-
-
Genres
-
- {anime.genres.map((genre) => (
-
- {genre}
-
- ))}
-
-
- )}
-
- {/* Synopsis */}
-
-
Synopsis
-
- {anime.synopsis}
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/AnimeRow.js b/src/components/AnimeRow.js
new file mode 100644
index 0000000..e153fb6
--- /dev/null
+++ b/src/components/AnimeRow.js
@@ -0,0 +1,131 @@
+'use client';
+
+import { useRef, useState, useEffect } from 'react';
+import AnimeCard from './AnimeCard';
+
+export default function AnimeRow({ title, animeList }) {
+ const scrollContainerRef = useRef(null);
+ const contentRef = useRef(null);
+ const [showLeftButton, setShowLeftButton] = useState(false);
+ const [showRightButton, setShowRightButton] = useState(false);
+
+ useEffect(() => {
+ if (!animeList || animeList.length <= 7) {
+ setShowRightButton(false);
+ return;
+ }
+
+ setShowRightButton(true);
+
+ const checkScroll = () => {
+ if (!scrollContainerRef.current) return;
+
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
+ setShowLeftButton(scrollLeft > 0);
+ setShowRightButton(scrollLeft + clientWidth < scrollWidth - 10);
+ };
+
+ const scrollContainer = scrollContainerRef.current;
+ scrollContainer.addEventListener('scroll', checkScroll);
+
+ // Initial check
+ checkScroll();
+
+ return () => {
+ if (scrollContainer) {
+ scrollContainer.removeEventListener('scroll', checkScroll);
+ }
+ };
+ }, [animeList]);
+
+ const scroll = (direction) => {
+ if (!scrollContainerRef.current) return;
+
+ const container = scrollContainerRef.current;
+ // Calculate single card width based on viewport
+ const isMobile = window.innerWidth < 640; // sm breakpoint in Tailwind
+ const cardsPerRow = isMobile ? 3 : 7;
+ const singleCardWidth = container.clientWidth / cardsPerRow;
+
+ if (direction === 'left') {
+ container.scrollBy({ left: -singleCardWidth, behavior: 'smooth' });
+ } else {
+ container.scrollBy({ left: singleCardWidth, behavior: 'smooth' });
+ }
+ };
+
+ if (!animeList || animeList.length === 0) return null;
+
+ // Create groups of cards for pagination - 3 for mobile, 7 for larger screens
+ const cardGroups = [];
+ const isMobileView = typeof window !== 'undefined' && window.innerWidth < 640;
+ const groupSize = isMobileView ? 3 : 7;
+
+ for (let i = 0; i < animeList.length; i += groupSize) {
+ cardGroups.push(animeList.slice(i, i + groupSize));
+ }
+
+ return (
+
+
+
{title}
+
+
+
+ {showLeftButton && (
+
+ )}
+
+
+
+ {cardGroups.map((group, groupIndex) => (
+
+ {group.map((anime, index) => (
+
+ ))}
+ {/* Add empty placeholders if needed to ensure slots are filled */}
+ {Array.from({ length: (typeof window !== 'undefined' && window.innerWidth < 640) ?
+ Math.max(0, 3 - group.length) :
+ Math.max(0, 7 - group.length) }).map((_, index) => (
+
+ ))}
+
+ ))}
+
+
+
+ {showRightButton && (
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/SeasonCard.js b/src/components/SeasonCard.js
new file mode 100644
index 0000000..2123710
--- /dev/null
+++ b/src/components/SeasonCard.js
@@ -0,0 +1,56 @@
+'use client';
+
+import Image from 'next/image';
+import Link from 'next/link';
+import { useState } from 'react';
+
+export default function SeasonCard({ season }) {
+ const [imageError, setImageError] = useState(false);
+
+ if (!season) return null;
+
+ const handleImageError = () => {
+ console.log("Image error for:", season.name);
+ setImageError(true);
+ };
+
+ // Get image URL with fallback
+ const imageSrc = imageError ? '/images/placeholder.png' : season.poster;
+
+ // Generate link
+ const infoLink = `/anime/${season.id}`;
+
+ return (
+
+
+ {/* Background image with blur */}
+
+
+ {/* Content overlay */}
+
+
+
+ {season.title || season.name}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/SeasonRow.js b/src/components/SeasonRow.js
new file mode 100644
index 0000000..e54c4b7
--- /dev/null
+++ b/src/components/SeasonRow.js
@@ -0,0 +1,162 @@
+'use client';
+
+import { useRef, useState, useEffect } from 'react';
+import SeasonCard from './SeasonCard';
+
+export default function SeasonRow({ title, seasons }) {
+ const scrollContainerRef = useRef(null);
+ const contentRef = useRef(null);
+ const [showLeftButton, setShowLeftButton] = useState(false);
+ const [showRightButton, setShowRightButton] = useState(false);
+
+ useEffect(() => {
+ if (!seasons || seasons.length <= 7) {
+ setShowRightButton(false);
+ return;
+ }
+
+ setShowRightButton(true);
+
+ const checkScroll = () => {
+ if (!scrollContainerRef.current) return;
+
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
+ setShowLeftButton(scrollLeft > 0);
+ setShowRightButton(scrollLeft + clientWidth < scrollWidth - 10);
+ };
+
+ const scrollContainer = scrollContainerRef.current;
+ scrollContainer.addEventListener('scroll', checkScroll);
+
+ // Initial check
+ checkScroll();
+
+ return () => {
+ if (scrollContainer) {
+ scrollContainer.removeEventListener('scroll', checkScroll);
+ }
+ };
+ }, [seasons]);
+
+ // Updated effect to handle mobile view arrows
+ useEffect(() => {
+ if (!seasons) return;
+
+ // Check if we're on mobile and have more than 3 seasons
+ const isMobileView = typeof window !== 'undefined' && window.innerWidth < 640;
+ const showArrowsOnMobile = isMobileView && seasons.length > 3;
+
+ // On desktop, show arrows if more than 7 seasons
+ const showArrowsOnDesktop = !isMobileView && seasons.length > 7;
+
+ if (showArrowsOnMobile || showArrowsOnDesktop) {
+ setShowRightButton(true);
+ } else {
+ setShowRightButton(false);
+ }
+
+ // Listen for resize events to update arrow visibility
+ const handleResize = () => {
+ const isMobile = window.innerWidth < 640;
+ const showArrows = isMobile ? seasons.length > 3 : seasons.length > 7;
+ setShowRightButton(showArrows);
+ };
+
+ window.addEventListener('resize', handleResize);
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, [seasons]);
+
+ const scroll = (direction) => {
+ if (!scrollContainerRef.current) return;
+
+ const container = scrollContainerRef.current;
+ // Calculate single card width based on viewport
+ const isMobile = window.innerWidth < 640; // sm breakpoint in Tailwind
+ const cardsPerRow = isMobile ? 3 : 7;
+ const singleCardWidth = container.clientWidth / cardsPerRow;
+
+ if (direction === 'left') {
+ container.scrollBy({ left: -singleCardWidth, behavior: 'smooth' });
+ } else {
+ container.scrollBy({ left: singleCardWidth, behavior: 'smooth' });
+ }
+ };
+
+ if (!seasons || seasons.length === 0) return null;
+
+ // Create groups of cards for pagination - 3 for mobile, 7 for larger screens
+ const seasonGroups = [];
+ const isMobileView = typeof window !== 'undefined' && window.innerWidth < 640;
+ const groupSize = isMobileView ? 3 : 7;
+
+ for (let i = 0; i < seasons.length; i += groupSize) {
+ seasonGroups.push(seasons.slice(i, i + groupSize));
+ }
+
+ return (
+
+
+
{title || 'Seasons'}
+
+
+
+ {showLeftButton && (
+
+ )}
+
+
+
+ {seasonGroups.map((group, groupIndex) => (
+
+ {group.map((season, index) => (
+
+
+
+ ))}
+ {/* Add empty placeholders if needed to ensure slots are filled */}
+ {Array.from({ length: (typeof window !== 'undefined' && window.innerWidth < 640) ?
+ Math.max(0, 3 - group.length) :
+ Math.max(0, 7 - group.length) }).map((_, index) => (
+
+ ))}
+
+ ))}
+
+
+
+ {showRightButton && (
+
+ )}
+
+
+ );
+}
\ No newline at end of file