Reader immersive mode: bars slide out after 5s, return on edge hover/tap
All checks were successful
Build and push Docker image / build (push) Successful in 12s
Test / test (push) Successful in 15s

After opening a book, a 5-second timer adds body.reader-immersive which:
- Slides navbar up (translateY(-100%))
- Slides now-playing bar down (translateY(100%))
- Expands reader overlay to full viewport (top:0, bottom:0)
- Collapses reader header (max-height:0)

Mouse near top edge (<60px): shows navbar + reader header
Mouse near bottom edge (<60px): shows now-playing bar
Touch at top/bottom edge: shows respective bar for 3s then hides again
closeReader() cleans up all classes and event listeners

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
marwin 2026-03-21 18:04:11 +01:00
parent 85776390f6
commit 0037fd8db4
2 changed files with 75 additions and 0 deletions

View file

@ -1653,6 +1653,25 @@ body.dnd-mode .timer-display {
/* PDF invert */
#reader-overlay.pdf-inverted .pdf-page { filter:invert(1); }
/* ---- Immersive reader mode ---- */
.navbar { transition: transform 0.35s ease; }
.now-playing-bar{ transition: transform 0.35s ease; }
.reader-overlay { transition: top 0.35s ease, bottom 0.35s ease; }
.reader-header { overflow: hidden; max-height: 60px;
transition: max-height 0.35s ease, padding-top 0.35s ease, padding-bottom 0.35s ease; }
body.reader-immersive .navbar { transform: translateY(-100%); }
body.reader-immersive .now-playing-bar { transform: translateY(100%); }
body.reader-immersive .reader-overlay { top: 0; bottom: 0; }
body.reader-immersive .reader-header { max-height: 0; padding-top: 0; padding-bottom: 0; }
body.reader-immersive.reader-show-top .navbar { transform: none; }
body.reader-immersive.reader-show-top .reader-overlay { top: var(--nav-h); }
body.reader-immersive.reader-show-top .reader-header { max-height: 60px; padding-top: 8px; padding-bottom: 8px; }
body.reader-immersive.reader-show-bottom .now-playing-bar { transform: none; }
body.reader-immersive.reader-show-bottom .reader-overlay { bottom: var(--bar-h); }
/* PDF paginated */
.reader-content.pdf-paginated { overflow:hidden !important; display:flex; align-items:center; justify-content:center; }
.pdf-paginated .pdf-page-wrapper { margin:0; }

View file

@ -2914,6 +2914,59 @@ async function renderPdf(arrayBuffer, contentEl, scaleOverride) {
return {title: pdfTitle, author: pdfAuthor, toc, numPages: pdf.numPages};
}
// ---------------------------------------------------------------------------
// Immersive reader mode — bars slide out after 5 s, return on edge hover/tap
// ---------------------------------------------------------------------------
let _immTimer = null;
let _immTopShowing = false;
let _immBottomShowing = false;
const _IMM_DELAY = 5000;
const _IMM_EDGE = 60; // px from top/bottom edge
function _immMouseMove(e) {
const atTop = e.clientY < _IMM_EDGE;
const atBottom = e.clientY > window.innerHeight - _IMM_EDGE;
if (atTop !== _immTopShowing) {
_immTopShowing = atTop;
document.body.classList.toggle('reader-show-top', atTop);
}
if (atBottom !== _immBottomShowing) {
_immBottomShowing = atBottom;
document.body.classList.toggle('reader-show-bottom', atBottom);
}
}
function _immTouch(e) {
const y = (e.touches[0] ?? e.changedTouches[0])?.clientY;
if (y == null) return;
if (y < _IMM_EDGE) {
document.body.classList.add('reader-show-top');
clearTimeout(_immTimer);
_immTimer = setTimeout(() => document.body.classList.remove('reader-show-top'), 3000);
} else if (y > window.innerHeight - _IMM_EDGE) {
document.body.classList.add('reader-show-bottom');
clearTimeout(_immTimer);
_immTimer = setTimeout(() => document.body.classList.remove('reader-show-bottom'), 3000);
}
}
function enterReaderImmersiveMode() {
clearTimeout(_immTimer);
_immTimer = setTimeout(() => document.body.classList.add('reader-immersive'), _IMM_DELAY);
window.addEventListener('mousemove', _immMouseMove);
window.addEventListener('touchstart', _immTouch, { passive: true });
}
function exitReaderImmersiveMode() {
clearTimeout(_immTimer);
_immTimer = null;
document.body.classList.remove('reader-immersive', 'reader-show-top', 'reader-show-bottom');
_immTopShowing = false;
_immBottomShowing = false;
window.removeEventListener('mousemove', _immMouseMove);
window.removeEventListener('touchstart', _immTouch);
}
async function openBook(bookId) {
const overlay = $('reader-overlay');
const contentEl = $('reader-content');
@ -3101,6 +3154,8 @@ async function openBook(bookId) {
}
}
enterReaderImmersiveMode();
} catch (e) {
overlay.style.display = 'none';
alert(`Failed to open book: ${e.message}`);
@ -3190,6 +3245,7 @@ async function saveReaderProgress() {
}
function closeReader() {
exitReaderImmersiveMode();
// Save progress BEFORE hiding — scrollHeight/clientHeight return 0 once display:none
saveReaderProgress();
if (bookmarksDirty) saveBookmarks();