const reader = document.getElementById('reader'); const panel = document.getElementById('settings-panel'); const overlay = document.getElementById('overlay'); const settingsBtn = document.getElementById('settings-btn'); const closePanel = document.getElementById('close-panel'); const chapterLabel = document.getElementById('chapter-label'); const prevBtn = document.getElementById('prev-chapter'); const nextBtn = document.getElementById('next-chapter'); const lnSettings = document.getElementById('ln-settings'); const mangaSettings = document.getElementById('manga-settings'); const config = { ln: { fontSize: 18, lineHeight: 1.8, maxWidth: 750, fontFamily: '"Georgia", serif', textColor: '#e5e7eb', bg: '#14141b', textAlign: 'justify' }, manga: { direction: 'rtl', mode: 'auto', spacing: 16, imageFit: 'screen', maxWidth: 900, quality: 'high', preloadCount: 3 } }; let currentType = null; let currentPages = []; let observer = null; const parts = window.location.pathname.split('/'); const bookId = parts[2]; let chapter = parts[3]; let provider = parts[4]; function loadConfig() { try { const saved = localStorage.getItem('readerConfig'); if (saved) { const parsed = JSON.parse(saved); Object.assign(config.ln, parsed.ln || {}); Object.assign(config.manga, parsed.manga || {}); } } catch (e) { console.error('Error loading config:', e); } updateUIFromConfig(); } function saveConfig() { try { localStorage.setItem('readerConfig', JSON.stringify(config)); } catch (e) { console.error('Error saving config:', e); } } function updateUIFromConfig() { // Light Novel document.getElementById('font-size').value = config.ln.fontSize; document.getElementById('font-size-value').textContent = config.ln.fontSize + 'px'; document.getElementById('line-height').value = config.ln.lineHeight; document.getElementById('line-height-value').textContent = config.ln.lineHeight; document.getElementById('max-width').value = config.ln.maxWidth; document.getElementById('max-width-value').textContent = config.ln.maxWidth + 'px'; document.getElementById('font-family').value = config.ln.fontFamily; document.getElementById('text-color').value = config.ln.textColor; document.getElementById('bg-color').value = config.ln.bg; // Text alignment buttons document.querySelectorAll('[data-align]').forEach(btn => { btn.classList.toggle('active', btn.dataset.align === config.ln.textAlign); }); // Manga document.getElementById('display-mode').value = config.manga.mode; document.getElementById('image-fit').value = config.manga.imageFit; document.getElementById('manga-max-width').value = config.manga.maxWidth; document.getElementById('manga-max-width-value').textContent = config.manga.maxWidth + 'px'; document.getElementById('page-spacing').value = config.manga.spacing; document.getElementById('page-spacing-value').textContent = config.manga.spacing + 'px'; document.getElementById('preload-count').value = config.manga.preloadCount; // Direction buttons document.querySelectorAll('[data-direction]').forEach(btn => { btn.classList.toggle('active', btn.dataset.direction === config.manga.direction); }); // Quality buttons document.querySelectorAll('[data-quality]').forEach(btn => { btn.classList.toggle('active', btn.dataset.quality === config.manga.quality); }); } function applyStyles() { if (currentType === 'ln') { document.documentElement.style.setProperty('--ln-font-size', config.ln.fontSize + 'px'); document.documentElement.style.setProperty('--ln-line-height', config.ln.lineHeight); document.documentElement.style.setProperty('--ln-max-width', config.ln.maxWidth + 'px'); document.documentElement.style.setProperty('--ln-font-family', config.ln.fontFamily); document.documentElement.style.setProperty('--ln-text-color', config.ln.textColor); document.documentElement.style.setProperty('--ln-bg', config.ln.bg); document.documentElement.style.setProperty('--ln-text-align', config.ln.textAlign); } if (currentType === 'manga') { document.documentElement.style.setProperty('--page-spacing', config.manga.spacing + 'px'); document.documentElement.style.setProperty('--page-max-width', config.manga.maxWidth + 'px'); document.documentElement.style.setProperty('--manga-max-width', config.manga.maxWidth + 'px'); const viewportHeight = window.innerHeight - 64 - 32; // header + padding document.documentElement.style.setProperty('--viewport-height', viewportHeight + 'px'); } } function updateSettingsVisibility() { lnSettings.classList.toggle('hidden', currentType !== 'ln'); mangaSettings.classList.toggle('hidden', currentType !== 'manga'); } async function loadChapter() { reader.innerHTML = `
Loading chapter...
`; try { const res = await fetch(`/api/book/${bookId}/${chapter}/${provider}`); const data = await res.json(); if (data.title) { chapterLabel.textContent = data.title; document.title = data.title; } else { chapterLabel.textContent = `Chapter ${chapter}`; document.title = `Chapter ${chapter}`; } if (data.error) { reader.innerHTML = `
Error: ${data.error}
`; return; } currentType = data.type; updateSettingsVisibility(); applyStyles(); reader.innerHTML = ''; if (data.type === 'manga') { currentPages = data.pages || []; loadManga(currentPages); } else if (data.type === 'ln') { loadLN(data.content); } } catch (error) { reader.innerHTML = `
❌ Error loading chapter: ${error.message}
`; } } function loadManga(pages) { if (!pages || pages.length === 0) { reader.innerHTML = '
No pages found
'; return; } const container = document.createElement('div'); container.className = 'manga-container'; const isLongStrip = config.manga.mode === 'longstrip' || (config.manga.mode === 'auto' && detectLongStrip(pages)); const useDouble = config.manga.mode === 'double' || (config.manga.mode === 'auto' && !isLongStrip && pages.length > 5); if (useDouble) { loadDoublePage(container, pages); } else { loadSinglePage(container, pages); } reader.appendChild(container); setupLazyLoading(); } function loadSinglePage(container, pages) { pages.forEach((page, index) => { const img = createImageElement(page.url, index); container.appendChild(img); }); } function loadDoublePage(container, pages) { for (let i = 0; i < pages.length; i += 2) { const doubleContainer = document.createElement('div'); doubleContainer.className = 'double-container'; const leftPage = createImageElement(pages[i].url, i); if (pages[i + 1]) { const rightPage = createImageElement(pages[i + 1].url, i + 1); if (config.manga.direction === 'rtl') { doubleContainer.appendChild(rightPage); doubleContainer.appendChild(leftPage); } else { doubleContainer.appendChild(leftPage); doubleContainer.appendChild(rightPage); } } else { doubleContainer.appendChild(leftPage); } container.appendChild(doubleContainer); } } function createImageElement(url, index) { const img = document.createElement('img'); img.className = 'page-img'; img.dataset.index = index; if (config.manga.imageFit === 'width') { img.classList.add('fit-width'); } else if (config.manga.imageFit === 'height') { img.classList.add('fit-height'); } else if (config.manga.imageFit === 'screen') { img.classList.add('fit-screen'); } // Preload o lazy load if (index < config.manga.preloadCount) { img.src = buildProxyUrl(url); } else { img.dataset.src = buildProxyUrl(url); img.loading = 'lazy'; } img.alt = `Page ${index + 1}`; return img; } function buildProxyUrl(url) { return `/api/proxy?url=${encodeURIComponent(url)}&referer=https%3A%2F%2Fmangapark.net`; } function detectLongStrip(pages) { if (!pages || pages.length === 0) return false; const tallPages = pages.filter(p => { if (!p.height || !p.width) return false; return (p.height / p.width) > 2.5; }); return tallPages.length >= Math.min(4, pages.length * 0.5); } function setupLazyLoading() { if (observer) observer.disconnect(); observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; if (img.dataset.src) { img.src = img.dataset.src; delete img.dataset.src; observer.unobserve(img); } } }); }, { rootMargin: '200px' }); document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img)); } function loadLN(html) { const div = document.createElement('div'); div.className = 'ln-content'; div.innerHTML = html; reader.appendChild(div); } document.getElementById('font-size').addEventListener('input', (e) => { config.ln.fontSize = parseInt(e.target.value); document.getElementById('font-size-value').textContent = e.target.value + 'px'; applyStyles(); saveConfig(); }); document.getElementById('line-height').addEventListener('input', (e) => { config.ln.lineHeight = parseFloat(e.target.value); document.getElementById('line-height-value').textContent = e.target.value; applyStyles(); saveConfig(); }); document.getElementById('max-width').addEventListener('input', (e) => { config.ln.maxWidth = parseInt(e.target.value); document.getElementById('max-width-value').textContent = e.target.value + 'px'; applyStyles(); saveConfig(); }); document.getElementById('font-family').addEventListener('change', (e) => { config.ln.fontFamily = e.target.value; applyStyles(); saveConfig(); }); document.getElementById('text-color').addEventListener('change', (e) => { config.ln.textColor = e.target.value; applyStyles(); saveConfig(); }); document.getElementById('bg-color').addEventListener('change', (e) => { config.ln.bg = e.target.value; applyStyles(); saveConfig(); }); // Text alignment document.querySelectorAll('[data-align]').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('[data-align]').forEach(b => b.classList.remove('active')); btn.classList.add('active'); config.ln.textAlign = btn.dataset.align; applyStyles(); saveConfig(); }); }); // Presets document.querySelectorAll('[data-preset]').forEach(btn => { btn.addEventListener('click', () => { const preset = btn.dataset.preset; const presets = { dark: { bg: '#14141b', textColor: '#e5e7eb' }, sepia: { bg: '#f4ecd8', textColor: '#5c472d' }, light: { bg: '#fafafa', textColor: '#1f2937' }, amoled: { bg: '#000000', textColor: '#ffffff' } }; if (presets[preset]) { Object.assign(config.ln, presets[preset]); document.getElementById('bg-color').value = config.ln.bg; document.getElementById('text-color').value = config.ln.textColor; applyStyles(); saveConfig(); } }); }); document.getElementById('display-mode').addEventListener('change', (e) => { config.manga.mode = e.target.value; saveConfig(); loadChapter(); }); document.getElementById('image-fit').addEventListener('change', (e) => { config.manga.imageFit = e.target.value; saveConfig(); loadChapter(); }); document.getElementById('manga-max-width').addEventListener('input', (e) => { config.manga.maxWidth = parseInt(e.target.value); document.getElementById('manga-max-width-value').textContent = e.target.value + 'px'; applyStyles(); saveConfig(); }); document.getElementById('page-spacing').addEventListener('input', (e) => { config.manga.spacing = parseInt(e.target.value); document.getElementById('page-spacing-value').textContent = e.target.value + 'px'; applyStyles(); saveConfig(); }); document.getElementById('preload-count').addEventListener('change', (e) => { config.manga.preloadCount = parseInt(e.target.value); saveConfig(); }); // Direction document.querySelectorAll('[data-direction]').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('[data-direction]').forEach(b => b.classList.remove('active')); btn.classList.add('active'); config.manga.direction = btn.dataset.direction; saveConfig(); loadChapter(); }); }); // Quality document.querySelectorAll('[data-quality]').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('[data-quality]').forEach(b => b.classList.remove('active')); btn.classList.add('active'); config.manga.quality = btn.dataset.quality; saveConfig(); }); }); prevBtn.addEventListener('click', () => { const newChapter = String(parseInt(chapter) - 1); updateURL(newChapter); window.scrollTo(0, 0); loadChapter(); }); nextBtn.addEventListener('click', () => { const newChapter = String(parseInt(chapter) + 1); updateURL(newChapter); window.scrollTo(0, 0); loadChapter(); }); function updateURL(newChapter) { chapter = newChapter; const newUrl = `/reader/${bookId}/${chapter}/${provider}`; window.history.pushState({}, '', newUrl); } document.getElementById('back-btn').addEventListener('click', () => { history.back(); }); settingsBtn.addEventListener('click', () => { panel.classList.add('open'); overlay.classList.add('active'); }); closePanel.addEventListener('click', closeSettings); overlay.addEventListener('click', closeSettings); function closeSettings() { panel.classList.remove('open'); overlay.classList.remove('active'); } document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && panel.classList.contains('open')) { closeSettings(); } }); document.addEventListener('keydown', (e) => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return; switch(e.key) { case 'ArrowLeft': if (config.manga.direction === 'rtl') { nextBtn.click(); } else { prevBtn.click(); } break; case 'ArrowRight': if (config.manga.direction === 'rtl') { prevBtn.click(); } else { nextBtn.click(); } break; case 's': case 'S': settingsBtn.click(); break; } }); let resizeTimer; window.addEventListener('resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { applyStyles(); }, 250); }); if (!bookId || !chapter || !provider) { reader.innerHTML = `
Missing required parameters (bookId, chapter, provider)
`; } else { loadConfig(); loadChapter(); }