diff --git a/app/public/app.js b/app/public/app.js
index c5c72b4..f9027b3 100644
--- a/app/public/app.js
+++ b/app/public/app.js
@@ -1,4 +1,36 @@
const app = document.getElementById('app')
+// Iconos SVG en línea (trazo estilo Lucide), heredan el color del texto.
+const ICONS = {
+ home: ' ',
+ history: ' ',
+ download: ' ',
+ settings: ' ',
+ play: ' ',
+ book: ' ',
+ stopRead: ' ',
+ stop: ' ',
+ trash: ' ',
+ refresh: ' ',
+ search: ' ',
+ sort: ' ',
+ back: ' ',
+ prev: ' ',
+ next: ' ',
+ more: ' ',
+ check: ' ',
+ login: ' ',
+ logout: ' ',
+ userPlus: ' ',
+ flame: ' ',
+ clock: ' ',
+ grid: ' ',
+ globe: ' ',
+ key: ' ',
+}
+const icon = name => `${ICONS[name]} `
+// Iconos del menú superior (index.html).
+document.querySelectorAll('#topbar nav a[data-icon]').forEach(a => { a.innerHTML = `${icon(a.dataset.icon)}${a.textContent} ` })
+
const SOURCES = { lc: 'LeerCapitulo (español)', md: 'MangaDex (multi-idioma)' }
// Filtros de imagen del lector (CSS filter); el brillo se suma aparte.
const FILTERS = {
@@ -114,8 +146,8 @@ function viewAuth(signup) {
- Entrar
- ${signup ? 'Crear cuenta ' : ''}
+ ${icon('login')} Entrar
+ ${signup ? `${icon('userPlus')} Crear cuenta ` : ''}
`
const form = $('form', app)
@@ -160,15 +192,15 @@ async function viewHome(params) {
app.innerHTML = `
${Object.entries(SOURCES).map(([k, v]) => `${v} `).join('')}
-
+
Incluir mangas sin traducción a ${esc(LANGS[me.lang] || me.lang)}
${q && !tag ? `Resultados para “${esc(q)}” ` : `
- Populares
- Actualizados recientemente
- Categorías
`}
+ ${icon('flame')} Populares
+ ${icon('clock')} Actualizados recientemente
+ ${icon('grid')} Categorías `}
${tag ? `Categoría: ${esc(tagName)} ✕ ${q ? ` · “${esc(q)}”` : ''} ` : ''}
Cargando…
- Cargar más
`
+ ${icon('more')} Cargar más
`
const form = $('form', app)
const go = (extra = {}) => {
const p = new URLSearchParams({ source, ...(form.q.value ? { q: form.q.value } : { mode }), ...(tag ? { tag, tagName } : {}), ...($('[name=any]').checked ? { any: '1' } : {}), ...extra })
@@ -230,11 +262,11 @@ async function viewManga(id, langOverride) {
const first = chapters[0]
let mainBtn = ''
if (progress && chapters.some(c => c.id === progress.chapter_id)) {
- mainBtn = `Continuar · ${progress.chapter_num ? `Cap. ${esc(progress.chapter_num)}` : 'Oneshot'}, pág. ${progress.page + 1} `
+ mainBtn = `${icon('play')} Continuar · ${progress.chapter_num ? `Cap. ${esc(progress.chapter_num)}` : 'Oneshot'}, pág. ${progress.page + 1} `
} else if (progress) {
- mainBtn = `Continuar · Cap. ${esc(progress.chapter_num ?? '?')} (${esc(LANGS[progress.lang] || progress.lang || '')}), pág. ${progress.page + 1} `
+ mainBtn = `${icon('play')} Continuar · Cap. ${esc(progress.chapter_num ?? '?')} (${esc(LANGS[progress.lang] || progress.lang || '')}), pág. ${progress.page + 1} `
}
- if (first && !mainBtn) mainBtn = `Empezar a leer `
+ if (first && !mainBtn) mainBtn = `${icon('book')} Empezar a leer `
app.innerHTML = `
@@ -245,9 +277,9 @@ async function viewManga(id, langOverride) {
${esc(manga.description)}
Fuente: ${esc(SOURCES[manga.source])}${download ? ` · Carpeta: mangas/${esc(download.folder)}` : ''}
${offline ? '
Sin conexión con la fuente: mostrando solo los capítulos descargados.
' : ''}
@@ -256,7 +288,7 @@ async function viewManga(id, langOverride) {
Capítulos (${chapters.length})
- ${chapters.length > 1 ? 'Invertir orden ' : ''}
+ ${chapters.length > 1 ? `${icon('sort')} Invertir orden ` : ''}
${chapters.length ? '' : isLc ? 'Este manga no tiene capítulos.
' : `No hay capítulos legibles en ${esc(LANGS[lang] || lang)} (MangaDex retira algunos por licencia).
Prueba a buscarlo en LeerCapitulo .
${chapterLangs.length ? `Disponibles en: ${chapterLangs.map(l => `${esc(LANGS[l] || l)} `).join(' ')}` : 'No hay capítulos en ningún idioma.'}
`}`
@@ -266,12 +298,12 @@ async function viewManga(id, langOverride) {
const box = $('#dl')
if (!box) return
const active = d && DL_ACTIVE.includes(d.status)
- const delBtn = d ? `Eliminar copia local${d.size ? ` (${fmtSize(d.size)})` : ''} ` : ''
+ const delBtn = d ? `${icon('trash')} Eliminar copia local${d.size ? ` (${fmtSize(d.size)})` : ''} ` : ''
const bar = d && (active || d.done < d.total) ? `
` : ''
let html
- if (!d || d.status === 'cancelled') html = `⬇ Descargar${d ? ` (llevas ${d.done}/${d.total})` : ''} ${delBtn}
${bar}`
- else if (active) html = `${downloadLabel(d)} · ${Math.floor(downloadPct(d))}% Cancelar ${delBtn}
${bar}${downloadDetail(d)}
`
- else html = `✓ ${downloadLabel(d)} ${d.status === 'error' ? 'Reintentar' : 'Buscar nuevos'} ${delBtn}
${bar}`
+ if (!d || d.status === 'cancelled') html = `${icon('download')} Descargar${d ? ` (llevas ${d.done}/${d.total})` : ''} ${delBtn}
${bar}`
+ else if (active) html = `${downloadLabel(d)} · ${Math.floor(downloadPct(d))}% ${icon('stop')} Cancelar ${delBtn}
${bar}${downloadDetail(d)}
`
+ else html = `${icon('check')} ${downloadLabel(d)} ${icon('refresh')} ${d.status === 'error' ? 'Reintentar' : 'Buscar nuevos'} ${delBtn}
${bar}`
box.innerHTML = offline ? (d ? `${downloadLabel(d)} ` : '') : html
$('#dlerr').textContent = d?.status === 'error' ? d.error || '' : ''
$('#dlgo')?.addEventListener('click', async () => {
@@ -312,7 +344,7 @@ async function viewManga(id, langOverride) {
const r = readMap.get(c.id)
const done = r && r.total_pages && r.page >= r.total_pages - 1
const cls = progress?.chapter_id === c.id ? 'current' : done ? 'read' : ''
- const meta = (dlSet.has(c.id) ? '⬇ ' : '') + (r ? (done ? 'Leído' : `pág. ${r.page + 1}/${r.total_pages}`) : esc(c.group))
+ const meta = (dlSet.has(c.id) ? `${icon('download')} ` : '') + (r ? (done ? 'Leído' : `pág. ${r.page + 1}/${r.total_pages}`) : esc(c.group))
return `${esc(chapLabel(c))} ${meta} `
}).join('')
}
@@ -358,7 +390,7 @@ async function viewReader(mangaId, chapterId, startPage) {
app.innerHTML = `
-
←
+
${icon('back')}
${esc(manga.title)}
${chapOpts}
Por página Vertical
@@ -370,10 +402,10 @@ async function viewReader(mangaId, chapterId, startPage) {
`
const reader = $('.reader', app)
@@ -391,8 +423,8 @@ async function viewReader(mangaId, chapterId, startPage) {
applyFilter()
if ($('#saver')) $('#saver').checked = settings.saver
const endBlock = ``
+ ${next ? `${icon('next')} Siguiente: ${esc(chapLabel(next))} ` : 'No hay más capítulos disponibles.
'}
+ ${icon('back')} Volver a la ficha `
const setPageLabel = () => { $('#pnum').textContent = `${page + 1} / ${total}`; slider.value = page + 1 }
let observer = null
@@ -509,8 +541,8 @@ async function viewHistory() {
${continueCard(h).replace('class="cont"', 'style="display:flex;gap:12px;flex:1;min-width:0;align-items:center"')}
`).join('')}`
app.querySelectorAll('[data-del]').forEach(b => b.addEventListener('click', async () => {
@@ -538,8 +570,8 @@ async function viewDownloads() {
${d.status === 'error' ? `${esc(d.error || '')}
` : ''}
- ${active ? `Cancelar ` : ''}
- Eliminar
+ ${active ? `${icon('stop')} Cancelar ` : ''}
+ ${icon('trash')} Eliminar
`
}).join('')}`
app.querySelectorAll('[data-cancel]').forEach(b => b.addEventListener('click', async () => { await api(`/api/download/${b.dataset.cancel}/cancel`, { method: 'POST' }); viewDownloads() }))
@@ -560,14 +592,14 @@ function viewSettings() {
app.innerHTML = `Ajustes
Cuenta Sesión iniciada como ${esc(me.username)}
- Cerrar sesión
+
${icon('logout')} Cerrar sesión
Idioma preferido de los capítulos
${Object.entries(LANGS).map(([k, v]) => `${v} `).join('')}
+
${icon('key')} Guardar
`
$('#logout').addEventListener('click', async () => { await api('/api/logout', { method: 'POST' }); me = null; location.hash = '#/login'; router() })
$('#lang').addEventListener('change', async e => { await api('/api/settings', { method: 'POST', body: { lang: e.target.value } }); me.lang = e.target.value; toast('Idioma guardado') })
diff --git a/app/public/index.html b/app/public/index.html
index 30b5385..1184f4d 100644
--- a/app/public/index.html
+++ b/app/public/index.html
@@ -12,10 +12,10 @@
diff --git a/app/public/style.css b/app/public/style.css
index f3c6feb..00671ab 100644
--- a/app/public/style.css
+++ b/app/public/style.css
@@ -129,3 +129,15 @@ a.tag:hover, .tag.on { background: var(--accent); color: var(--accent-ink); }
.dlbar { height: 8px; border-radius: 4px; overflow: hidden; }
.dlbar > i { transition: width .6s ease; }
.history .cont a.info { display: block; min-width: 0; flex: 1; }
+
+/* Iconos */
+.ic { width: 1.15em; height: 1.15em; flex: none; vertical-align: -0.2em; }
+#topbar nav a { display: inline-flex; align-items: center; gap: 6px; }
+.tabs button { display: inline-flex; align-items: center; gap: 6px; }
+.chapters .meta .ic { width: 1em; height: 1em; }
+@media (max-width: 560px) {
+ #topbar nav a span { display: none; } /* en móvil solo iconos en el menú */
+ #topbar nav a { padding: 8px 10px; }
+ #topbar nav a .ic { width: 1.35em; height: 1.35em; }
+ .search .btn .lbl { display: none; }
+}