feat: chapter chevrons, sticky app frame, and focus mode (0.21.0)

Reader gets chapter-aware chevrons + a persistent app frame +
distraction-free focus mode.

- Single-mode chevrons (and ArrowLeft/Right + j/k) advance pages
  within the chapter and fall through to the adjacent chapter at the
  boundaries. Last page of last chapter / first page of first
  chapter disables the chevron and silent-no-ops on the keypress.
- Continuous-mode gets a fixed bottom bar with prev/next chapter
  buttons; arrows + j/k jump chapters directly.
- `?page=N` and `?page=last` URL query lets the prev-chapter jump
  land on the previous chapter's last page.
- Layout header is fixed at the top; reader nav is sticky just
  below it; both stay visible while scrolling so reading settings
  are always reachable.
- New "Focus" toggle in the reader nav hides the layout header,
  reader nav, and bottom chapter bar with smooth 220ms slide
  animations. Exit via Esc or a small floating Minimize2 button at
  the top-right (low resting opacity, full on hover). Reset on
  reader unmount so it doesn't leak to other pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-17 20:39:03 +02:00
parent c95c1805df
commit 7aa6e7e6d9
8 changed files with 468 additions and 29 deletions

View File

@@ -0,0 +1,33 @@
/**
* Cross-component flag for the reader's "hide all chrome" view.
*
* The reader page toggles this; the root layout reads it to hide the
* top app navbar; the reader itself reads it to hide its own nav bar
* and bottom chapter bar. CSS handles the slide animations via a
* `data-reader-fullscreen` attribute on `<html>` so the entire frame
* (layout chrome included) stays synchronised.
*
* Always reset on reader unmount — letting the flag leak across
* navigation would orphan a hidden app navbar on other pages.
*/
let active = $state(false);
export const readerFullscreen = {
get value() {
return active;
},
set value(v: boolean) {
active = v;
if (typeof document !== 'undefined') {
if (v) document.documentElement.dataset.readerFullscreen = 'true';
else delete document.documentElement.dataset.readerFullscreen;
}
},
toggle() {
this.value = !active;
},
/** Force off — call from the reader's onDestroy. */
reset() {
this.value = false;
}
};

View File

@@ -60,6 +60,13 @@
--icon-md: 18px;
--icon-lg: 22px;
/* App-frame heights (fixed-position bars at the top and bottom of
the viewport). Used by the layout + reader to reserve content
space and animate fullscreen mode. Recomputed once if the
header padding/font-size ever changes — keep in sync. */
--app-header-h: 60px;
--reader-bar-h: 56px;
--z-dropdown: 10;
--z-sticky: 50;
--z-modal: 100;