189 lines
6.2 KiB
JavaScript
189 lines
6.2 KiB
JavaScript
let trendingBooks = [];
|
|
let currentHeroIndex = 0;
|
|
let heroInterval;
|
|
|
|
// --- NAVBAR SCROLL ---
|
|
window.addEventListener('scroll', () => {
|
|
const nav = document.getElementById('navbar');
|
|
if (window.scrollY > 50) nav.classList.add('scrolled');
|
|
else nav.classList.remove('scrolled');
|
|
});
|
|
|
|
// --- SEARCH LOGIC ---
|
|
const searchInput = document.getElementById('search-input');
|
|
const searchResults = document.getElementById('search-results');
|
|
let searchTimeout;
|
|
|
|
searchInput.addEventListener('input', (e) => {
|
|
const query = e.target.value;
|
|
clearTimeout(searchTimeout);
|
|
|
|
if (query.length < 2) {
|
|
searchResults.classList.remove('active');
|
|
searchResults.innerHTML = '';
|
|
searchInput.style.borderRadius = '99px';
|
|
return;
|
|
}
|
|
|
|
// Debounce 300ms
|
|
searchTimeout = setTimeout(() => {
|
|
fetchBookSearch(query);
|
|
}, 300);
|
|
});
|
|
|
|
// Hide results on outside click
|
|
document.addEventListener('click', (e) => {
|
|
if (!e.target.closest('.search-wrapper')) {
|
|
searchResults.classList.remove('active');
|
|
searchInput.style.borderRadius = '99px';
|
|
}
|
|
});
|
|
|
|
async function fetchBookSearch(query) {
|
|
try {
|
|
const res = await fetch(`/api/search/books?q=${encodeURIComponent(query)}`);
|
|
const data = await res.json();
|
|
renderSearchResults(data.results || []);
|
|
} catch (err) {
|
|
console.error("Search Error:", err);
|
|
renderSearchResults([]);
|
|
}
|
|
}
|
|
|
|
function renderSearchResults(results) {
|
|
searchResults.innerHTML = '';
|
|
|
|
if (!results || results.length === 0) {
|
|
searchResults.innerHTML = '<div style="padding:1rem; color:#888; text-align:center">No results found</div>';
|
|
} else {
|
|
results.forEach(book => {
|
|
const title = book.title.english || book.title.romaji || "Unknown";
|
|
const img = (book.coverImage && (book.coverImage.medium || book.coverImage.large)) || '';
|
|
const rating = book.averageScore ? `${book.averageScore}%` : 'N/A';
|
|
const year = book.seasonYear || (book.startDate ? book.startDate.year : '') || '????';
|
|
const format = book.format || 'MANGA';
|
|
|
|
const item = document.createElement('a');
|
|
item.className = 'search-item';
|
|
item.href = `/book/${book.id}`; // Direct navigation link
|
|
|
|
item.innerHTML = `
|
|
<img src="${img}" class="search-poster" alt="${title}">
|
|
<div class="search-info">
|
|
<div class="search-title">${title}</div>
|
|
<div class="search-meta">
|
|
<span class="rating-pill">${rating}</span>
|
|
<span>• ${year}</span>
|
|
<span>• ${format}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
searchResults.appendChild(item);
|
|
});
|
|
}
|
|
|
|
searchResults.classList.add('active');
|
|
searchInput.style.borderRadius = '12px 12px 0 0';
|
|
}
|
|
|
|
// --- CAROUSEL LOGIC ---
|
|
function scrollCarousel(id, direction) {
|
|
const container = document.getElementById(id);
|
|
if(container) {
|
|
const scrollAmount = container.clientWidth * 0.75;
|
|
container.scrollBy({ left: direction * scrollAmount, behavior: 'smooth' });
|
|
}
|
|
}
|
|
|
|
// --- FETCH DATA ---
|
|
async function init() {
|
|
try {
|
|
// Fetch Trending
|
|
const res = await fetch('/api/books/trending');
|
|
const data = await res.json();
|
|
|
|
if (data.results && data.results.length > 0) {
|
|
trendingBooks = data.results;
|
|
updateHeroUI(trendingBooks[0]);
|
|
renderList('trending', trendingBooks);
|
|
startHeroCycle();
|
|
}
|
|
|
|
// Fetch Popular
|
|
const resPop = await fetch('/api/books/popular');
|
|
const dataPop = await resPop.json();
|
|
if (dataPop.results) renderList('popular', dataPop.results);
|
|
|
|
} catch (e) {
|
|
console.error("Books Error:", e);
|
|
}
|
|
}
|
|
|
|
// --- HERO LOGIC ---
|
|
function startHeroCycle() {
|
|
if(heroInterval) clearInterval(heroInterval);
|
|
heroInterval = setInterval(() => {
|
|
if(trendingBooks.length > 0) {
|
|
currentHeroIndex = (currentHeroIndex + 1) % trendingBooks.length;
|
|
updateHeroUI(trendingBooks[currentHeroIndex]);
|
|
}
|
|
}, 8000);
|
|
}
|
|
|
|
function updateHeroUI(book) {
|
|
if(!book) return;
|
|
const title = book.title.english || book.title.romaji;
|
|
const desc = book.description || "No description available.";
|
|
const poster = (book.coverImage && (book.coverImage.extraLarge || book.coverImage.large)) || '';
|
|
const banner = book.bannerImage || poster;
|
|
|
|
document.getElementById('hero-title').innerText = title;
|
|
document.getElementById('hero-desc').innerHTML = desc;
|
|
document.getElementById('hero-score').innerText = (book.averageScore || '?') + '% Score';
|
|
document.getElementById('hero-year').innerText = (book.startDate && book.startDate.year) ? book.startDate.year : '????';
|
|
document.getElementById('hero-type').innerText = book.format || 'MANGA';
|
|
|
|
const heroPoster = document.getElementById('hero-poster');
|
|
if(heroPoster) heroPoster.src = poster;
|
|
|
|
// Update background
|
|
const bg = document.getElementById('hero-bg-media');
|
|
if(bg) bg.src = banner;
|
|
|
|
// Setup Read Now Button
|
|
const readBtn = document.getElementById('read-btn');
|
|
if (readBtn) {
|
|
readBtn.onclick = () => window.location.href = `/book/${book.id}`;
|
|
}
|
|
}
|
|
|
|
// --- RENDER LIST ---
|
|
function renderList(id, list) {
|
|
const container = document.getElementById(id);
|
|
container.innerHTML = '';
|
|
|
|
list.forEach(book => {
|
|
const title = book.title.english || book.title.romaji;
|
|
const cover = book.coverImage ? book.coverImage.large : '';
|
|
const score = book.averageScore || '--';
|
|
const type = book.format || 'Book';
|
|
|
|
const el = document.createElement('div');
|
|
el.className = 'card';
|
|
el.onclick = () => {
|
|
// Navigate to book page
|
|
window.location.href = `/book/${book.id}`;
|
|
};
|
|
el.innerHTML = `
|
|
<div class="card-img-wrap"><img src="${cover}" loading="lazy"></div>
|
|
<div class="card-content">
|
|
<h3>${title}</h3>
|
|
<p>${score}% • ${type}</p>
|
|
</div>
|
|
`;
|
|
container.appendChild(el);
|
|
});
|
|
}
|
|
|
|
init(); |