From 2b524439bfebcd5f8f5e7bd52342659a160296ee Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Fri, 3 Jul 2026 21:14:02 +0900 Subject: [PATCH] fix(reader): keep running header/footer readable over light PDFs in dark mode (#4901) (#4911) The running section title and page-number footer used text-neutral-content, which is a light color in dark mode. A light-mode PDF stays white under a dark theme (invertImgColorInDark defaults to false), so the light text sat on the white page and became unreadable. Blend the header/footer text against whatever is behind it using mix-blend-mode: difference with a fixed white/75 anchor, so it inverts to dark on a light page and stays light on a dark margin. white/75 matches the former neutral-content brightness over the dark theme, so reflowable books look unchanged. E-ink keeps its plain base-content text; StatusInfo and the sticky progress bar manage their own colors and are left untouched. Co-authored-by: Claude Fable 5 --- .../reader/components/SectionInfo.test.tsx | 24 ++++++++++ .../__tests__/components/ProgressBar.test.tsx | 48 +++++++++++++++++++ .../src/app/reader/components/ProgressBar.tsx | 6 +++ .../src/app/reader/components/SectionInfo.tsx | 8 +++- 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/apps/readest-app/src/__tests__/app/reader/components/SectionInfo.test.tsx b/apps/readest-app/src/__tests__/app/reader/components/SectionInfo.test.tsx index 9d74d524..60bf2313 100644 --- a/apps/readest-app/src/__tests__/app/reader/components/SectionInfo.test.tsx +++ b/apps/readest-app/src/__tests__/app/reader/components/SectionInfo.test.tsx @@ -68,3 +68,27 @@ describe('SectionInfo notch mask', () => { expect(notch.classList.contains('bg-base-100')).toBe(false); }); }); + +describe('SectionInfo contrast against the page (#4901)', () => { + // A light-mode PDF shown under a dark theme keeps its white page, but the + // running header text was themed for the dark UI (neutral-content, light) + // and became unreadable on the white page. mix-blend-mode: difference makes + // the text invert against whatever is actually behind it (the page or the + // themed margin), so it stays legible over any background. + it('blends the section title against the background in non-eink mode', () => { + const { container } = render(); + const info = container.querySelector('.sectioninfo') as HTMLElement; + + expect(info.classList.contains('mix-blend-difference')).toBe(true); + expect(info.classList.contains('text-white/75')).toBe(true); + // The theme-fixed neutral color is what made it unreadable on a white page. + expect(info.classList.contains('text-neutral-content')).toBe(false); + }); + + it('does not blend in eink mode (base-content text on the e-ink page)', () => { + const { container } = render(); + const info = container.querySelector('.sectioninfo') as HTMLElement; + + expect(info.classList.contains('mix-blend-difference')).toBe(false); + }); +}); diff --git a/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx b/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx index 653eb147..dcf98809 100644 --- a/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx +++ b/apps/readest-app/src/__tests__/components/ProgressBar.test.tsx @@ -286,3 +286,51 @@ describe('ProgressBar — sticky progress bar', () => { expect(container.querySelector('.sticky-progress-bar')).toBeNull(); }); }); + +describe('ProgressBar — contrast against the page (#4901)', () => { + // A light-mode PDF under a dark theme keeps its white page while the footer + // progress text stayed themed for the dark UI (neutral-content, light) and + // became unreadable over the white page. Blending the text spans against the + // real backdrop keeps the page number and remaining-pages text legible on + // any background. StatusInfo (clock/battery) is intentionally left alone — it + // manages its own blend against the battery glyph. + it('blends the progress and remaining text against the background in non-eink mode', () => { + currentViewSettings = { + ...baseSettings, + isEink: false, + showRemainingPages: true, + showRemainingTime: false, + progressInfoMode: 'all', + } as ViewSettings; + currentProgress = makeProgress(2, 5); + currentBookData = { isFixedLayout: false }; + currentRenderer = { page: 1, pages: 4 }; + + const { container } = renderProgressBar(); + + const progress = container.querySelector('.progress-info') as HTMLElement; + const remaining = container.querySelector('.remaining-info') as HTMLElement; + expect(progress.classList.contains('mix-blend-difference')).toBe(true); + expect(progress.classList.contains('text-white/75')).toBe(true); + expect(remaining.classList.contains('mix-blend-difference')).toBe(true); + expect(remaining.classList.contains('text-white/75')).toBe(true); + }); + + it('does not blend in eink mode', () => { + currentViewSettings = { + ...baseSettings, + isEink: true, + showRemainingPages: true, + showRemainingTime: false, + progressInfoMode: 'all', + } as ViewSettings; + currentProgress = makeProgress(2, 5); + currentBookData = { isFixedLayout: false }; + currentRenderer = { page: 1, pages: 4 }; + + const { container } = renderProgressBar(); + + const progress = container.querySelector('.progress-info') as HTMLElement; + expect(progress.classList.contains('mix-blend-difference')).toBe(false); + }); +}); diff --git a/apps/readest-app/src/app/reader/components/ProgressBar.tsx b/apps/readest-app/src/app/reader/components/ProgressBar.tsx index 40ed2b21..8ca61c4b 100644 --- a/apps/readest-app/src/app/reader/components/ProgressBar.tsx +++ b/apps/readest-app/src/app/reader/components/ProgressBar.tsx @@ -287,6 +287,9 @@ const ProgressBar: React.FC = ({ 'remaining-info whitespace-nowrap text-start', !stickyBarActive && 'flex-1', showStatusInfo && 'overflow-hidden', + // Keep the text legible on any backdrop (e.g. a light PDF page + // under a dark theme, #4901); blend it against what's behind. + !isEink && 'text-white/75 mix-blend-difference', )} > {viewSettings.showRemainingTime ? ( @@ -346,6 +349,9 @@ const ProgressBar: React.FC = ({ className={clsx( 'progress-info items-center overflow-hidden whitespace-nowrap text-end tabular-nums', !stickyBarActive && 'flex-1', + // Keep the page number legible on any backdrop (e.g. a light PDF + // page under a dark theme, #4901); blend it against what's behind. + !isEink && 'text-white/75 mix-blend-difference', )} > {(progressBarMode === 'all' || progressBarMode.includes('progress')) && ( diff --git a/apps/readest-app/src/app/reader/components/SectionInfo.tsx b/apps/readest-app/src/app/reader/components/SectionInfo.tsx index 7cc4938c..f4d35a85 100644 --- a/apps/readest-app/src/app/reader/components/SectionInfo.tsx +++ b/apps/readest-app/src/app/reader/components/SectionInfo.tsx @@ -75,7 +75,13 @@ const SectionInfo: React.FC = ({