const animeId = window.location.pathname.split('/').pop(); let player; let totalEpisodes = 0; let currentPage = 1; const itemsPerPage = 12; var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); let extensionName; async function loadAnime() { try { const path = window.location.pathname; const parts = path.split("/").filter(Boolean); let animeId; if (parts.length === 3) { extensionName = parts[1]; animeId = parts[2]; } else { animeId = parts[1]; } const fetchUrl = extensionName ? `/api/anime/${animeId.slice(0,40)}?ext=${extensionName}` : `/api/anime/${animeId}`; const res = await fetch(fetchUrl); const data = await res.json(); if(data.error) { document.getElementById('title').innerText = "Anime Not Found"; return; } const title = data.title?.english || data.title?.romaji || "Unknown Title"; document.title = `${title} | WaifuBoard`; document.getElementById('title').innerText = title; if (data.coverImage?.extraLarge) { document.getElementById('poster').src = data.coverImage.extraLarge; } const rawDesc = data.description || "No description available."; handleDescription(rawDesc); document.getElementById('score').innerText = (data.averageScore || '?') + '% Score'; document.getElementById('year').innerText = data.seasonYear || data.startDate?.year || '????'; document.getElementById('genres').innerText = data.genres?.length > 0 ? data.genres.slice(0, 3).join(' • ') : ''; document.getElementById('format').innerText = data.format || 'TV'; document.getElementById('status').innerText = data.status || 'Unknown'; const extensionPill = document.getElementById('extension-pill'); if (extensionName && extensionPill) { extensionPill.textContent = `${extensionName.charAt(0).toUpperCase() + extensionName.slice(1).toLowerCase()}`; extensionPill.style.display = 'inline-flex'; } else if (extensionPill) { extensionPill.style.display = 'none'; } let seasonText = ''; if (data.season && data.seasonYear) { seasonText = `${data.season} ${data.seasonYear}`; } else if (data.startDate?.year) { const months = ['', 'Winter', 'Winter', 'Spring', 'Spring', 'Spring', 'Summer', 'Summer', 'Summer', 'Fall', 'Fall', 'Fall', 'Winter']; const month = data.startDate.month || 1; const estimatedSeason = months[month] || ''; seasonText = `${estimatedSeason} ${data.startDate.year}`.trim(); } document.getElementById('season').innerText = seasonText || 'Unknown'; let studioName = 'Unknown Studio'; if (data.studios?.nodes?.length > 0) { studioName = data.studios.nodes[0].name; } else if (data.studios?.edges?.length > 0) { studioName = data.studios.edges[0]?.node?.name || 'Unknown Studio'; } document.getElementById('studio').innerText = studioName; const charContainer = document.getElementById('char-list'); charContainer.innerHTML = ''; let characters = []; if (data.characters?.nodes?.length > 0) { characters = data.characters.nodes.slice(0, 5); } else if (data.characters?.edges?.length > 0) { characters = data.characters.edges .filter(edge => edge?.node?.name?.full) .slice(0, 5) .map(edge => edge.node); } if (characters.length > 0) { characters.forEach(char => { if (char?.name?.full) { charContainer.innerHTML += `
${char.name.full}
`; } }); } else { charContainer.innerHTML = `
No character data available
`; } document.getElementById('watch-btn').onclick = () => { window.location.href = `/watch/${animeId}/1`; }; if (data.trailer && data.trailer.site === 'youtube') { window.onYouTubeIframeAPIReady = function() { player = new YT.Player('player', { height: '100%', width: '100%', videoId: data.trailer.id, playerVars: { 'autoplay': 1, 'controls': 0, 'mute': 1, 'loop': 1, 'playlist': data.trailer.id, 'showinfo': 0, 'modestbranding': 1, 'disablekb': 1 }, events: { 'onReady': (e) => e.target.playVideo() } }); }; } else { const banner = data.bannerImage || data.coverImage?.extraLarge || ''; if (banner) { document.querySelector('.video-background').innerHTML = ``; } } let extensionEpisodes = []; if (extensionName) { extensionEpisodes = await loadExtensionEpisodes(animeId, extensionName); if (extensionEpisodes.length > 0) { totalEpisodes = extensionEpisodes.length; } else { totalEpisodes = 1; } } else { // MODO NORMAL (AniList) if (data.nextAiringEpisode?.episode) { totalEpisodes = data.nextAiringEpisode.episode - 1; } else if (data.episodes) { totalEpisodes = data.episodes; } else { totalEpisodes = 12; } } totalEpisodes = Math.min(Math.max(totalEpisodes, 1), 5000); document.getElementById('episodes').innerText = totalEpisodes; renderEpisodes(); } catch (err) { console.error('Error loading anime:', err); document.getElementById('title').innerText = "Error loading anime"; } } async function loadExtensionEpisodes(animeId, extName) { try { const url = `/api/anime/${animeId}/episodes?ext=${extName}`; const res = await fetch(url); const data = await res.json(); if (!Array.isArray(data)) return []; return data.map(ep => ({ id: ep.id, number: ep.number, title: ep.title || `Episode ${ep.number}`, url: ep.url })); } catch (err) { console.error("Failed to fetch extension episodes:", err); return []; } } function handleDescription(text) { const tmp = document.createElement("DIV"); tmp.innerHTML = text; const cleanText = tmp.textContent || tmp.innerText || ""; const sentences = cleanText.match(/[^\.!\?]+[\.!\?]+/g) || [cleanText]; document.getElementById('full-description').innerHTML = text; if (sentences.length > 4) { const shortText = sentences.slice(0, 4).join(' '); document.getElementById('description-preview').innerText = shortText + '...'; document.getElementById('read-more-btn').style.display = 'inline-flex'; } else { document.getElementById('description-preview').innerHTML = text; document.getElementById('read-more-btn').style.display = 'none'; } } function openModal() { document.getElementById('desc-modal').classList.add('active'); document.body.style.overflow = 'hidden'; } function closeModal() { document.getElementById('desc-modal').classList.remove('active'); document.body.style.overflow = ''; } document.getElementById('desc-modal').addEventListener('click', (e) => { if (e.target.id === 'desc-modal') closeModal(); }); function renderEpisodes() { const grid = document.getElementById('episodes-grid'); grid.innerHTML = ''; const start = (currentPage - 1) * itemsPerPage + 1; const end = Math.min(start + itemsPerPage - 1, totalEpisodes); for(let i = start; i <= end; i++) { createEpisodeButton(i, grid); } updatePaginationControls(); } function createEpisodeButton(num, container) { const btn = document.createElement('div'); btn.className = 'episode-btn'; btn.innerText = `Ep ${num}`; btn.onclick = () => window.location.href = `/watch/${animeId}/${num}` + (extensionName ? `?${extensionName}` : ""); container.appendChild(btn); } function updatePaginationControls() { const totalPages = Math.ceil(totalEpisodes / itemsPerPage); document.getElementById('page-info').innerText = `Page ${currentPage} of ${totalPages}`; document.getElementById('prev-page').disabled = currentPage === 1; document.getElementById('next-page').disabled = currentPage === totalPages; document.getElementById('pagination-controls').style.display = 'flex'; } function changePage(delta) { currentPage += delta; renderEpisodes(); } const searchInput = document.getElementById('ep-search'); searchInput.addEventListener('input', (e) => { const val = parseInt(e.target.value); const grid = document.getElementById('episodes-grid'); if (val > 0 && val <= totalEpisodes) { grid.innerHTML = ''; createEpisodeButton(val, grid); document.getElementById('pagination-controls').style.display = 'none'; } else if (!e.target.value) { renderEpisodes(); } else { grid.innerHTML = '
Episode not found
'; document.getElementById('pagination-controls').style.display = 'none'; } }); loadAnime();