// Chapter markers for manga/comics // Visual indicators for chapter boundaries // Procedural implementation (no OOP) interface ChapterInfo { chapterNumber: number; pageStart: number; pageEnd: number; title?: string; } interface ChapterMarkerState { chapters: ChapterInfo[]; currentChapter: number; showMarkers: boolean; } function createChapterMarkerState( chapters: ChapterInfo[], currentPage: number, ): ChapterMarkerState { const currentChapter = chapters.find((c) => currentPage >= c.pageStart && currentPage <= c.pageEnd) ?.chapterNumber || 1; return { chapters, currentChapter, showMarkers: true, }; } function renderChapterMarkers( container: HTMLElement, state: ChapterMarkerState, ): void { if (!state.showMarkers) return; const markersContainer = document.createElement("div"); markersContainer.className = "chapter-markers absolute left-0 right-0 pointer-events-none z-10"; state.chapters.forEach((chapter) => { const marker = document.createElement("div"); marker.className = "chapter-marker flex items-center gap-2 text-sm text-gray-400"; const isCurrentChapter = chapter.chapterNumber === state.currentChapter; marker.style.position = "absolute"; marker.style.top = `${((chapter.pageStart - 1) / 100) * 100}%`; marker.style.left = "10px"; marker.innerHTML = ` ${chapter.title || `Chapter ${chapter.chapterNumber}`} p.${chapter.pageStart} ${isCurrentChapter ? '' : ""} `; markersContainer.appendChild(marker); }); const existing = container.querySelector(".chapter-markers"); existing?.remove(); container.appendChild(markersContainer); } function updateCurrentChapter( state: ChapterMarkerState, currentPage: number, ): ChapterMarkerState { const currentChapter = state.chapters.find( (c) => currentPage >= c.pageStart && currentPage <= c.pageEnd, )?.chapterNumber || state.currentChapter; if (currentChapter !== state.currentChapter) { const newState = { ...state, currentChapter }; const markers = document.querySelector(".chapter-markers"); if (markers) { renderChapterMarkers(markers.parentElement!, newState); } return newState; } return state; } function toggleChapterMarkers(state: ChapterMarkerState): ChapterMarkerState { const newState = { ...state, showMarkers: !state.showMarkers }; const markers = document.querySelector(".chapter-markers"); if (markers) { markers.classList.toggle("hidden", !newState.showMarkers); } return newState; } function scrollToChapter( state: ChapterMarkerState, chapterNumber: number, ): void { const chapter = state.chapters.find((c) => c.chapterNumber === chapterNumber); if (chapter) { window.dispatchEvent( new CustomEvent("navigate-to-page", { detail: { page: chapter.pageStart }, }), ); } } const chapterMarkerCSS = ` .chapter-marker { padding: 4px 8px; margin-left: -18px; opacity: 0.7; transition: opacity 0.2s; } .chapter-marker:hover { opacity: 1; } .chapter-marker .current-indicator { color: #3b82f6; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .chapter-marker-line { position: absolute; left: 0; right: 0; height: 1px; background: linear-gradient(to right, rgba(255,255,255,0.1), transparent); } `;