From 06dcf0a649b0c24ed1579c1dc2c19c26af16e8fc Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sun, 12 Apr 2026 10:17:54 +0800 Subject: [PATCH] Rewrite reader for iOS Safari with continuous multi-chapter flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the fixed-position reader with a sticky layout that works correctly on iPhone Safari and Edge, while also auto-appending the next chapter's pages when the current one finishes. Layout - Swap all position:fixed for sticky (Header, BottomNav, reader top nav) — fixed-positioning quirks broke the bottom nav in Edge and prevented Safari's URL bar from collapsing on scroll - Viewport: viewport-fit=cover + interactiveWidget=overlays-content so manga extends edge-to-edge and the URL bar overlays content without resizing the viewport - Add pt-safe / pb-safe utilities; apply on nav bars so chrome respects the notch and home-indicator - Drop fixed-positioning bottom padding now that BottomNav is in flow Continuous reading - PageReader now receives the full chapter manifest (id + totalPages) and auto-fetches the next chapter when the current one is done - Subtle chapter divider strip appears between chapters in the scroll - Top nav chapter title updates as the user scrolls into a new chapter (rAF-throttled scroll listener, cached offsetTop) - Double-tap on left/right viewport half navigates prev/next chapter - End-of-manga footer fills the viewport with a Back-to-Manga action Theme polish - Light theme: white body/background, blue accent preserved for chapter numbers, badges, active states - Modern chapter drawer: white sheet, rounded-t-3xl, two-column rows with chapter-number badge, blue highlight for current chapter - Suppress hydration warnings for extension-injected attributes on and the search input - Manga detail CTA localized to 开始阅读 Co-Authored-By: Claude Opus 4.6 (1M context) --- app/globals.css | 10 +- app/layout.tsx | 18 +- app/manga/[slug]/[chapter]/page.tsx | 7 +- app/manga/[slug]/page.tsx | 2 +- components/BottomNav.tsx | 2 +- components/Header.tsx | 2 +- components/PageReader.tsx | 418 ++++++++++++++++++---------- components/SearchBar.tsx | 1 + 8 files changed, 295 insertions(+), 165 deletions(-) diff --git a/app/globals.css b/app/globals.css index 4ab3327..e67a0e1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -30,8 +30,12 @@ -webkit-tap-highlight-color: transparent; } +html, +body { + background-color: var(--background); +} + body { - background: var(--background); color: var(--foreground); font-family: var(--font-sans), system-ui, sans-serif; overflow-x: hidden; @@ -71,4 +75,8 @@ html { .pb-safe { padding-bottom: env(safe-area-inset-bottom); } + .pt-safe { + padding-top: env(safe-area-inset-top); + } } + diff --git a/app/layout.tsx b/app/layout.tsx index b3502ee..c894b95 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -28,7 +28,12 @@ export const viewport: Viewport = { initialScale: 1, maximumScale: 1, viewportFit: "cover", - themeColor: "#ffffff", + interactiveWidget: "overlays-content", + colorScheme: "light", + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#ffffff" }, + ], }; export default function RootLayout({ @@ -37,10 +42,15 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - + +
-
{children}
+
{children}
diff --git a/app/manga/[slug]/[chapter]/page.tsx b/app/manga/[slug]/[chapter]/page.tsx index 2c165c9..cd8aefb 100644 --- a/app/manga/[slug]/[chapter]/page.tsx +++ b/app/manga/[slug]/[chapter]/page.tsx @@ -54,18 +54,17 @@ export default async function ChapterReaderPage({ params }: Props) { : null; const allChapters = manga.chapters.map((c) => ({ + id: c.id, number: c.number, title: c.title, + totalPages: c._count.pages, })); return ( - Start Reading — Ch. {manga.chapters[0].number} + 开始阅读 )} diff --git a/components/BottomNav.tsx b/components/BottomNav.tsx index bf5889b..978fa15 100644 --- a/components/BottomNav.tsx +++ b/components/BottomNav.tsx @@ -47,7 +47,7 @@ export function BottomNav() { } return ( -