/* ========================================================= manuales-factorpesca / manual.js Lógica de presentación del manual: - Genera el índice automático a partir de h1, h2, h3. - Dibuja anotaciones numeradas sobre las capturas. - Captura el nombre del capítulo actual para la cabecera. - Numera figuras y pasos (complementa a los CSS counters). - Prepara el contador de página tras la carga de fuentes. Este script NO añade interactividad: solo prepara el DOM para que Chromium imprima un PDF bien paginado. ========================================================= */ (function () { 'use strict'; document.addEventListener('DOMContentLoaded', init); function init() { if (document.fonts && document.fonts.ready) { document.fonts.ready.then(afterFonts); } else { afterFonts(); } } function afterFonts() { try { buildTOC(); renderAnnotations(); setupChapterString(); ensureAnchors(); ensureImagePaths(); } catch (err) { console.error('[manual.js] Error preparando el manual:', err); } } /* --------------------------------------------------------- 1. Índice automático --------------------------------------------------------- */ function buildTOC() { const out = document.querySelector('[data-toc-output]'); if (!out) return; const chapters = document.querySelectorAll('[data-chapter]'); if (!chapters.length) { out.innerHTML = '
  • Sin capítulos definidos.
  • '; return; } const frag = document.createDocumentFragment(); let chapterIndex = 0; chapters.forEach((chapter) => { chapterIndex += 1; const title = chapter.querySelector('[data-chapter-title]') || chapter.querySelector('h1'); if (!title) return; const li = document.createElement('li'); li.className = 'toc-h1'; li.appendChild(tocRow(romanize(chapterIndex) + '. ' + title.textContent.trim())); frag.appendChild(li); const subsections = chapter.querySelectorAll('h2'); subsections.forEach((sub) => { const row = document.createElement('li'); row.className = 'toc-h2'; row.appendChild(tocRow(' ' + (sub.dataset.tocLabel || sub.textContent.trim()))); frag.appendChild(row); }); const subsub = chapter.querySelectorAll('h3'); subsub.forEach((sub) => { const row = document.createElement('li'); row.className = 'toc-h3'; row.appendChild(tocRow(' ' + (sub.dataset.tocLabel || sub.textContent.trim()))); frag.appendChild(row); }); }); out.innerHTML = ''; out.appendChild(frag); } function tocRow(label) { const span = document.createElement('span'); span.className = 'toc-row'; const text = document.createElement('span'); text.className = 'toc-text'; text.textContent = label; const dots = document.createElement('span'); dots.className = 'toc-dots'; const num = document.createElement('span'); num.className = 'toc-page-num'; num.textContent = '—'; span.appendChild(text); span.appendChild(dots); span.appendChild(num); return span; } function romanize(num) { const map = [ ['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1], ]; let result = ''; let n = num; for (const [letter, value] of map) { while (n >= value) { result += letter; n -= value; } } return result; } /* --------------------------------------------------------- 2. Anotaciones sobre capturas --------------------------------------------------------- */ function renderAnnotations() { document.querySelectorAll('.annotations[data-annotations]').forEach((list) => { let data = []; try { data = JSON.parse(list.getAttribute('data-annotations') || '[]'); } catch (err) { console.warn('[manual.js] data-annotations inválido:', err); return; } if (!Array.isArray(data) || data.length === 0) { list.remove(); return; } const sorted = data.slice().sort((a, b) => (a.n || 0) - (b.n || 0)); sorted.forEach((entry) => { const li = document.createElement('li'); li.className = 'annotation'; li.textContent = String(entry.n); if (typeof entry.x === 'number' && typeof entry.y === 'number') { li.style.left = entry.x + '%'; li.style.top = entry.y + '%'; } if (entry.label) { li.setAttribute('aria-label', entry.label); li.setAttribute('title', entry.label); } list.appendChild(li); }); }); } /* --------------------------------------------------------- 3. Cabecera dinámica con el nombre del capítulo actual --------------------------------------------------------- */ function setupChapterString() { const chapters = document.querySelectorAll('[data-chapter]'); chapters.forEach((chapter) => { const title = chapter.querySelector('[data-chapter-title]') || chapter.querySelector('h1'); if (!title) return; title.setAttribute('data-string', title.textContent.trim()); }); } /* --------------------------------------------------------- 4. Anclas automáticas para headings --------------------------------------------------------- */ function ensureAnchors() { const headings = document.querySelectorAll('[data-chapter] h1, [data-chapter] h2, [data-chapter] h3, [data-chapter] h4'); headings.forEach((h) => { if (!h.id) { const slug = (h.textContent || '') .toLowerCase() .normalize('NFD').replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, ''); h.id = slug || 'h-' + Math.random().toString(36).slice(2, 8); } }); } /* --------------------------------------------------------- 5. Resolver rutas de imágenes a absolutas --------------------------------------------------------- */ function ensureImagePaths() { const base = window.location.href.replace(/[^/]*$/, ''); document.querySelectorAll('img').forEach((img) => { const src = img.getAttribute('src'); if (!src || src.startsWith('http') || src.startsWith('data:') || src.startsWith('file:')) return; const absolute = new URL(src, base).href; img.src = absolute; }); } })();