From 1c5c25e5dd3b1377f4781a3eb351d2b5177e4a91 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Apr 2026 15:37:29 +0500 Subject: [PATCH] feat(reader): add lines-per-page setting for paginated mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a PageLines preference (Few/Normal/Many) that adjusts the paginated container height via a rem offset on the existing calc(). The setting row appears in Reader Settings → Layout only when Pages mode is active, matching the style of all other setting rows. Persisted in localStorage (reader_layout_v1). --- .../books/[slug]/chapters/[n]/+page.svelte | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/ui/src/routes/books/[slug]/chapters/[n]/+page.svelte b/ui/src/routes/books/[slug]/chapters/[n]/+page.svelte index d5eb3c4..ebfac2d 100644 --- a/ui/src/routes/books/[slug]/chapters/[n]/+page.svelte +++ b/ui/src/routes/books/[slug]/chapters/[n]/+page.svelte @@ -53,6 +53,8 @@ type ReadWidth = 'narrow' | 'normal' | 'wide'; type ParaStyle = 'spaced' | 'indented'; type PlayerStyle = 'standard' | 'compact'; + /** Controls how many lines fit on a page by adjusting the container height offset. */ + type PageLines = 'less' | 'normal' | 'more'; interface LayoutPrefs { readMode: ReadMode; @@ -61,12 +63,19 @@ paraStyle: ParaStyle; focusMode: boolean; playerStyle: PlayerStyle; + pageLines: PageLines; } const LAYOUT_KEY = 'reader_layout_v1'; - const LINE_HEIGHTS: Record = { compact: 1.55, normal: 1.85, relaxed: 2.2 }; - const READ_WIDTHS: Record = { narrow: '58ch', normal: '72ch', wide: 'min(90ch, 100%)' }; - const DEFAULT_LAYOUT: LayoutPrefs = { readMode: 'scroll', lineSpacing: 'normal', readWidth: 'normal', paraStyle: 'spaced', focusMode: false, playerStyle: 'standard' }; + const LINE_HEIGHTS: Record = { compact: 1.55, normal: 1.85, relaxed: 2.2 }; + const READ_WIDTHS: Record = { narrow: '58ch', normal: '72ch', wide: 'min(90ch, 100%)' }; + /** + * Extra rem subtracted from (or added to) the paginated container height. + * Normal (0rem) keeps the existing calc(); Less (-4rem) makes the container + * shorter so fewer lines fit per page; More (+4rem) grows it for more lines. + */ + const PAGE_LINES_OFFSET: Record = { less: '4rem', normal: '0rem', more: '-4rem' }; + const DEFAULT_LAYOUT: LayoutPrefs = { readMode: 'scroll', lineSpacing: 'normal', readWidth: 'normal', paraStyle: 'spaced', focusMode: false, playerStyle: 'standard', pageLines: 'normal' }; function loadLayout(): LayoutPrefs { if (!browser) return DEFAULT_LAYOUT; @@ -114,8 +123,10 @@ $effect(() => { if (layout.readMode !== 'paginated') { pageIndex = 0; totalPages = 1; return; } - // Re-run when html changes or container is bound + // Re-run when html, container refs, or mini-player visibility changes + // (mini-player adds pb-24 which reduces the available viewport height) void html; void paginatedContainerEl; void paginatedContentEl; + void audioStore.active; void audioExpanded; requestAnimationFrame(() => { if (!paginatedContainerEl || !paginatedContentEl) return; const h = paginatedContainerEl.clientHeight; @@ -542,7 +553,9 @@ role="none" bind:this={paginatedContainerEl} class="paginated-container mt-8" - style="height: {layout.focusMode ? 'calc(100svh - 8rem)' : 'calc(100svh - 26rem)'};" + style="height: {layout.focusMode + ? 'calc(100svh - 8rem)' + : `calc(100svh - 26rem - ${PAGE_LINES_OFFSET[layout.pageLines]})`};" onclick={handlePaginatedClick} >
+ {#if layout.readMode === 'paginated'} +
+ Lines +
+ {#each ([['less', 'Few'], ['normal', 'Normal'], ['more', 'Many']] as const) as [v, lbl]} + + {/each} +
+
+ {/if} +
Spacing