mangaverse-dark / script.js
SquatchDev's picture
Manga reading website. The design is very intentional, with each section's layout tailored to its specific purpose.
6b53737 verified
// MangaVerse Dark - Main Script
class MangaApp {
constructor() {
this.currentPage = 1;
this.chaptersPerPage = 12;
this.init();
}
init() {
this.loadTrending();
this.loadPopularAdditions();
this.loadNewChapters();
this.loadRecommendedLists();
this.loadBlogPosts();
this.initializeCarousels();
this.initializeFilters();
}
// API helper
async fetchMangaData(endpoint, params = {}) {
try {
const queryString = new URLSearchParams(params).toString();
const response = await fetch(`https://api.jikan.moe/v4/${endpoint}?${queryString}`);
if (!response.ok) throw new Error('API request failed');
return await response.json();
} catch (error) {
console.error('Error fetching manga data:', error);
return { data: [] };
}
}
// Trending Section - 2-row grid, landscape cards
async loadTrending() {
const container = document.getElementById('trending-grid');
const data = await this.fetchMangaData('manga', {
order_by: 'popularity',
sort: 'desc',
limit: 6
});
if (!data.data || data.data.length === 0) {
container.innerHTML = this.generateSkeletons(6);
return;
}
container.innerHTML = data.data.map((manga, index) => `
<a href="#manga/${manga.mal_id}" class="manga-card group relative rounded-xl overflow-hidden bg-gray-800 shadow-lg">
<div class="aspect-manga-landscape relative">
<img src="${manga.images?.jpg?.large_image_url || 'http://static.photos/nature/320x240/' + (index + 1)}"
alt="${manga.title}"
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
<div class="absolute inset-0 gradient-overlay"></div>
<div class="absolute bottom-0 left-0 right-0 p-4">
<span class="inline-block px-2 py-1 text-xs font-semibold bg-gray-700 rounded mb-2">
${manga.type || 'Manga'}
</span>
<h3 class="font-bold text-sm line-clamp-2 group-hover:text-gray-300 transition-colors">
${manga.title}
</h3>
</div>
</div>
</a>
`).join('');
}
// Popular Additions - Horizontal scroll, portrait cards with metadata
async loadPopularAdditions() {
const container = document.getElementById('popular-carousel');
const data = await this.fetchMangaData('manga', {
order_by: 'score',
sort: 'desc',
limit: 8
});
if (!data.data || data.data.length === 0) {
container.innerHTML = this.generateSkeletons(8, 'portrait');
return;
}
container.innerHTML = data.data.map((manga, index) => `
<a href="#manga/${manga.mal_id}" class="manga-card group flex-shrink-0 w-48 bg-gray-800 rounded-xl overflow-hidden shadow-lg">
<div class="aspect-manga-portrait relative overflow-hidden">
<img src="${manga.images?.jpg?.large_image_url || 'http://static.photos/nature/200x300/' + (index + 1)}"
alt="${manga.title}"
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105">
</div>
<div class="p-4">
<div class="flex flex-wrap gap-1 mb-2">
${(manga.genres?.slice(0, 2) || []).map(genre => `
<span class="text-xs px-2 py-1 bg-gray-700 rounded">${genre.name}</span>
`).join('')}
</div>
<h3 class="font-bold text-sm mb-3 line-clamp-2 group-hover:text-gray-300">
${manga.title}
</h3>
<div class="space-y-2 text-xs text-gray-400">
<div class="flex items-center justify-between">
<span class="flex items-center">
<i data-feather="book" class="w-3 h-3 mr-1"></i>
${manga.chapters || '??'} capítulos
</span>
<span class="flex items-center">
<i data-feather="eye" class="w-3 h-3 mr-1"></i>
${this.formatNumber(manga.scored_by || Math.floor(Math.random() * 50000) + 1000)}
</span>
</div>
<div class="flex items-center justify-between">
<span class="flex items-center">
<i data-feather="heart" class="w-3 h-3 mr-1"></i>
${this.formatNumber(Math.floor(manga.members * 0.1) || Math.floor(Math.random() * 10000) + 500)}
</span>
<span class="flex items-center">
<i data-feather="star" class="w-3 h-3 mr-1"></i>
${manga.score || 'N/A'}
</span>
</div>
</div>
</div>
</a>
`).join('');
// Re-render feather icons for new content
feather.replace();
}
// New Chapters - Two-column list view
async loadNewChapters() {
const container = document.getElementById('chapters-list');
const data = await this.fetchMangaData('manga', {
order_by: 'start_date',
sort: 'desc',
limit: 20
});
if (!data.data || data.data.length === 0) {
container.innerHTML = this.generateListSkeletons(12);
return;
}
const chapters = data.data.flatMap(manga => {
const chapterCount = manga.chapters || Math.floor(Math.random() * 80) + 10;
const latestChapter = Math.floor(Math.random() * chapterCount) + 1;
return [{
manga_id: manga.mal_id,
title: manga.title,
chapter: latestChapter,
timestamp: this.generateRandomTimestamp(),
scan_group: this.getRandomScanGroup(),
thumbnail: manga.images?.jpg?.image_url
}];
});
this.renderChapters(chapters.slice(0, this.chaptersPerPage));
this.setupChapterPagination(chapters.length);
}
renderChapters(chapters) {
const container = document.getElementById('chapters-list');
container.innerHTML = chapters.map(chapter => `
<a href="#manga/${chapter.manga_id}/chapter/${chapter.chapter}"
class="group flex items-center p-4 bg-gray-800 rounded-lg hover:bg-gray-750 transition-all duration-300 hover:pl-6 overflow-hidden">
<div class="w-16 h-20 flex-shrink-0 rounded overflow-hidden mr-4 bg-gray-700">
<img src="${chapter.thumbnail || 'http://static.photos/nature/200x250/' + (chapter.manga_id % 100)}"
alt="${chapter.title}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300">
</div>
<div class="flex-grow min-w-0">
<h3 class="font-bold text-base mb-1 group-hover:text-gray-300 transition-colors truncate">
${chapter.title}
</h3>
<div class="flex items-center flex-wrap gap-4 text-sm text-gray-400">
<span class="flex items-center">
<i data-feather="book-open" class="w-4 h-4 mr-1"></i>
Capítulo ${chapter.chapter}
</span>
<span class="flex items-center">
<i data-feather="users" class="w-4 h-4 mr-1"></i>
${chapter.scan_group}
</span>
<span class="flex items-center">
<i data-feather="clock" class="w-4 h-4 mr-1"></i>
${chapter.timestamp}
</span>
</div>
</div>
<div class="flex-shrink-0 ml-4">
<span class="px-2 py-1 text-xs bg-gray-700 rounded-full pulse-new">Nuevo</span>
</div>
</a>
`).join('');
feather.replace();
}
// Recommended Lists - Horizontal scroll
loadRecommendedLists() {
const container = document.getElementById('lists-carousel');
const lists = [
{ name: 'Fantasía Épica', count: 33, color: 'purple', icon: 'sword' },
{ name: 'Romance Shoujo', count: 28, color: 'pink', icon: 'heart' },
{ name: 'Cyberpunk', count: 15, color: 'blue', icon: 'cpu' },
{ name: 'Misterio Psicológico', count: 22, color: 'gray', icon: 'eye' },
{ name: 'Aventura Isekai', count: 41, color: 'green', icon: 'compass' },
{ name: 'Horror Gore', count: 18, color: 'red', icon: 'alert-triangle' },
{ name: 'Slice of Life', count: 35, color: 'yellow', icon: 'coffee' },
{ name: 'Deportes', count: 12, color: 'orange', icon: 'activity' }
];
container.innerHTML = lists.map(list => `
<a href="#lista/${list.name.toLowerCase().replace(/\s+/g, '-')}"
class="manga-card group flex-shrink-0 w-48 bg-gradient-to-br from-gray-800 to-gray-850 rounded-xl p-6 shadow-lg hover:shadow-xl transition-all duration-300">
<div class="text-center">
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-700 flex items-center justify-center group-hover:scale-110 transition-transform">
<i data-feather="${list.icon}" class="w-8 h-8 text-gray-400"></i>
</div>
<h3 class="font-bold text-lg mb-2 group-hover:text-gray-300">
${list.name}
</h3>
<p class="text-sm text-gray-400">
<span class="font-semibold text-gray-300">${list.count}</span> obras
</p>
</div>
</a>
`).join('');
feather.replace();
}
// Blog Section - 3-column grid
loadBlogPosts() {
const container = document.getElementById('blog-grid');
const posts = [
{
title: 'Los mejores animes de primavera 2024',
excerpt: 'Descubre qué series están dominando la temporada y cuáles no te puedes perder.',
image: 'http://static.photos/nature/640x360/101',
category: 'Temporada',
date: '2 días atrás'
},
{
title: 'Guía completa de Demon Slayer',
excerpt: 'Todo lo que necesitas saber sobre el fenómeno que revolucionó el manga moderno.',
image: 'http://static.photos/nature/640x360/102',
category: 'Guía',
date: '5 días atrás'
},
{
title: 'Entrevista exclusiva con Tatsuki Fujimoto',
excerpt: 'El autor de Chainsaw Man nos habla de su proceso creativo y futuros proyectos.',
image: 'http://static.photos/nature/640x360/103',
category: 'Entrevista',
date: '1 semana atrás'
}
];
container.innerHTML = posts.map(post => `
<a href="#blog/${post.title.toLowerCase().replace(/\s+/g, '-')}"
class="manga-card group bg-gray-800 rounded-xl overflow-hidden shadow-lg">
<div class="aspect-video relative overflow-hidden">
<img src="${post.image}"
alt="${post.title}"
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-3">
<span class="text-xs px-3 py-1 bg-gray-700 rounded-full">${post.category}</span>
<span class="text-xs text-gray-400 flex items-center">
<i data-feather="calendar" class="w-3 h-3 mr-1"></i>
${post.date}
</span>
</div>
<h3 class="font-bold text-xl mb-3 line-clamp-2 group-hover:text-gray-300">
${post.title}
</h3>
<p class="text-gray-400 line-clamp-3 mb-4">
${post.excerpt}
</p>
<div class="flex items-center text-gray-400 group-hover:text-gray-300 font-semibold">
Leer artículo
<i data-feather="arrow-right" class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform"></i>
</div>
</div>
</a>
`).join('');
feather.replace();
}
// Carousel controls
initializeCarousels() {
// Popular carousel
const popularCarousel = document.getElementById('popular-carousel');
const popularPrev = document.getElementById('popular-prev');
const popularNext = document.getElementById('popular-next');
if (popularPrev && popularNext) {
popularPrev.addEventListener('click', () => {
popularCarousel.scrollBy({ left: -400, behavior: 'smooth' });
});
popularNext.addEventListener('click', () => {
popularCarousel.scrollBy({ left: 400, behavior: 'smooth' });
});
}
// Lists carousel
const listsCarousel = document.getElementById('lists-carousel');
const listsPrev = document.getElementById('lists-prev');
const listsNext = document.getElementById('lists-next');
if (listsPrev && listsNext) {
listsPrev.addEventListener('click', () => {
listsCarousel.scrollBy({ left: -300, behavior: 'smooth' });
});
listsNext.addEventListener('click', () => {
listsCarousel.scrollBy({ left: 300, behavior: 'smooth' });
});
}
}
// Chapter filter
initializeFilters() {
const filter = document.getElementById('chapter-filter');
if (filter) {
filter.addEventListener('change', (e) => {
// In a real app, this would filter the API results
console.log('Filter changed to:', e.target.value);
this.loadNewChapters();
});
}
}
// Pagination setup
setupChapterPagination(totalItems) {
const container = document.getElementById('chapters-pagination');
const totalPages = Math.ceil(totalItems / this.chaptersPerPage);
container.innerHTML = `
<button class="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg font-semibold transition-colors ${this.currentPage === 1 ? 'opacity-50 cursor-not-allowed' : ''}"
${this.currentPage === 1 ? 'disabled' : ''}>
<i data-feather="chevron-left" class="w-4 h-4"></i>
Anterior
</button>
${Array.from({ length: Math.min(totalPages, 5) }, (_, i) => `
<button class="px-4 py-2 rounded-lg font-semibold transition-colors ${i + 1 === this.currentPage ? 'bg-gray-600' : 'bg-gray-800 hover:bg-gray-700'}">
${i + 1}
</button>
`).join('')}
<button class="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg font-semibold transition-colors ${this.currentPage === totalPages ? 'opacity-50 cursor-not-allowed' : ''}"
${this.currentPage === totalPages ? 'disabled' : ''}>
Siguiente
<i data-feather="chevron-right" class="w-4 h-4"></i>
</button>
`;
feather.replace();
}
// Utility functions
formatNumber(num) {
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
if (num >= 1000) return (num / 1000).toFixed(1) + 'k';
return num.toString();
}
generateRandomTimestamp() {
const times = ['Hace 15 min', 'Hace 1 hora', 'Hace 3 horas', 'Hace 6 horas', 'Hace 12 horas', 'Ayer', 'Hace 2 días'];
return times[Math.floor(Math.random() * times.length)];
}
getRandomScanGroup() {
const groups = ['MangoScan', 'NeoFansub', 'KawaiiScans', 'DarkManga', 'SkyTranslations', 'MoonlitScans'];
return groups[Math.floor(Math.random() * groups.length)];
}
generateSkeletons(count, type = 'landscape') {
const aspectClass = type === 'portrait' ? 'aspect-manga-portrait' : 'aspect-manga-landscape';
return Array(count).fill(`
<div class="${aspectClass} bg-gray-800 rounded-xl skeleton"></div>
`).join('');
}
generateListSkeletons(count) {
return Array(count).fill(`
<div class="flex items-center p-4 bg-gray-800 rounded-lg skeleton">
<div class="w-16 h-20 rounded mr-4"></div>
<div class="flex-grow">
<div class="h-4 w-3/4 rounded mb-2"></div>
<div class="h-3 w-1/2 rounded"></div>
</div>
</div>
`).join('');
}
}
// Initialize app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new MangaApp();
// Add scroll-based navbar effect
const navbar = document.querySelector('manga-navbar');
if (navbar) {
window.addEventListener('scroll', () => {
const scrolled = window.pageYOffset > 50;
navbar.shadowRoot.querySelector('nav').classList.toggle('bg-opacity-95', scrolled);
navbar.shadowRoot.querySelector('nav').classList.toggle('backdrop-blur', scrolled);
});
}
});
// Handle navigation
window.addEventListener('hashchange', () => {
const hash = window.location.hash;
if (hash.startsWith('#manga/')) {
console.log('Navigate to manga:', hash);
// In a real app, this would load manga detail page
}
});