diff --git a/books/core/index.html b/books/core/index.html index ddad3f8..26cf0ab 100644 --- a/books/core/index.html +++ b/books/core/index.html @@ -187,365 +187,365 @@

359 pages

@@ -20540,7 +20540,7 @@ the GM's eye: it's level 10 and a Bestiary ( { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); }); - btnNext.addEventListener('click', () => { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); }); + btnPrev.addEventListener('click', () => { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }); + btnNext.addEventListener('click', () => { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }); document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; - if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); } - if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); } + if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); } + if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); } }); - // Track current page by scroll position + // Track current page by scroll position (replaceState — no new history entry) content.addEventListener('scroll', () => { const containerTop = content.getBoundingClientRect().top; let found = 0; @@ -20583,13 +20584,22 @@ the GM's eye: it's level 10 and a Bestiary ( { + const pm = location.hash.match(/^#page-(\d+)$/); + if (pm) { + const n = parseInt(pm[1], 10); + if (PAGES.includes(n)) goTo(n, false, false); + } + }); + // Initial navigation from URL hash const m = location.hash.match(/^#page-(\d+)$/); const startPage = m ? parseInt(m[1], 10) : PAGES[0]; - goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false); + goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false, false); diff --git a/books/natural-fantasy-atlas/index.html b/books/natural-fantasy-atlas/index.html index 80307e4..9498c04 100644 --- a/books/natural-fantasy-atlas/index.html +++ b/books/natural-fantasy-atlas/index.html @@ -187,216 +187,216 @@

210 pages

@@ -1261,7 +1261,7 @@ const indicator = document.getElementById('page-indicator'); const titleEl = document.getElementById('page-title'); - function updateNav(idx) { + function updateNav(idx, push) { if (idx === currentIdx && indicator.textContent) return; currentIdx = idx; const n = PAGES[idx]; @@ -1274,28 +1274,29 @@ }); document.querySelector('#page-list li[data-page="' + n + '"]') ?.scrollIntoView({ block: 'nearest' }); - history.replaceState(null, '', '#page-' + n); + if (push) history.pushState(null, '', '#page-' + n); + else history.replaceState(null, '', '#page-' + n); } - function goTo(n, smooth) { + function goTo(n, smooth, push) { const idx = PAGES.indexOf(n); if (idx === -1) return; const sec = document.getElementById('page-' + n); if (!sec) return; sec.scrollIntoView({ behavior: smooth ? 'smooth' : 'instant', block: 'start' }); - updateNav(idx); + updateNav(idx, push); } - btnPrev.addEventListener('click', () => { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); }); - btnNext.addEventListener('click', () => { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); }); + btnPrev.addEventListener('click', () => { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }); + btnNext.addEventListener('click', () => { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }); document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; - if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); } - if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); } + if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); } + if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); } }); - // Track current page by scroll position + // Track current page by scroll position (replaceState — no new history entry) content.addEventListener('scroll', () => { const containerTop = content.getBoundingClientRect().top; let found = 0; @@ -1304,13 +1305,22 @@ if (sec && sec.getBoundingClientRect().top - containerTop <= 40) found = i; else break; } - if (found !== currentIdx) updateNav(found); + if (found !== currentIdx) updateNav(found, false); }, { passive: true }); + // Browser back/forward + window.addEventListener('popstate', () => { + const pm = location.hash.match(/^#page-(\d+)$/); + if (pm) { + const n = parseInt(pm[1], 10); + if (PAGES.includes(n)) goTo(n, false, false); + } + }); + // Initial navigation from URL hash const m = location.hash.match(/^#page-(\d+)$/); const startPage = m ? parseInt(m[1], 10) : PAGES[0]; - goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false); + goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false, false); diff --git a/build.py b/build.py index a4cd311..8d04584 100644 --- a/build.py +++ b/build.py @@ -20,7 +20,7 @@ def build_index(title, DIR): sections = [(n, read_page(n)) for n in page_nums] sidebar_items = '\n '.join( - f'
  • ' for n in page_nums ) @@ -250,7 +250,7 @@ def build_index(title, DIR): const indicator = document.getElementById('page-indicator'); const titleEl = document.getElementById('page-title'); - function updateNav(idx) {{ + function updateNav(idx, push) {{ if (idx === currentIdx && indicator.textContent) return; currentIdx = idx; const n = PAGES[idx]; @@ -263,28 +263,29 @@ def build_index(title, DIR): }}); document.querySelector('#page-list li[data-page="' + n + '"]') ?.scrollIntoView({{ block: 'nearest' }}); - history.replaceState(null, '', '#page-' + n); + if (push) history.pushState(null, '', '#page-' + n); + else history.replaceState(null, '', '#page-' + n); }} - function goTo(n, smooth) {{ + function goTo(n, smooth, push) {{ const idx = PAGES.indexOf(n); if (idx === -1) return; const sec = document.getElementById('page-' + n); if (!sec) return; sec.scrollIntoView({{ behavior: smooth ? 'smooth' : 'instant', block: 'start' }}); - updateNav(idx); + updateNav(idx, push); }} - btnPrev.addEventListener('click', () => {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); }}); - btnNext.addEventListener('click', () => {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); }}); + btnPrev.addEventListener('click', () => {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }}); + btnNext.addEventListener('click', () => {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }}); document.addEventListener('keydown', e => {{ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; - if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true); }} - if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true); }} + if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {{ if (currentIdx > 0) goTo(PAGES[currentIdx - 1], true, true); }} + if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {{ if (currentIdx < total - 1) goTo(PAGES[currentIdx + 1], true, true); }} }}); - // Track current page by scroll position + // Track current page by scroll position (replaceState — no new history entry) content.addEventListener('scroll', () => {{ const containerTop = content.getBoundingClientRect().top; let found = 0; @@ -293,13 +294,22 @@ def build_index(title, DIR): if (sec && sec.getBoundingClientRect().top - containerTop <= 40) found = i; else break; }} - if (found !== currentIdx) updateNav(found); + if (found !== currentIdx) updateNav(found, false); }}, {{ passive: true }}); + // Browser back/forward + window.addEventListener('popstate', () => {{ + const pm = location.hash.match(/^#page-(\\d+)$/); + if (pm) {{ + const n = parseInt(pm[1], 10); + if (PAGES.includes(n)) goTo(n, false, false); + }} + }}); + // Initial navigation from URL hash const m = location.hash.match(/^#page-(\\d+)$/); const startPage = m ? parseInt(m[1], 10) : PAGES[0]; - goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false); + goTo(PAGES.includes(startPage) ? startPage : PAGES[0], false, false);