yiekheng 06dcf0a649 Rewrite reader for iOS Safari with continuous multi-chapter flow
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
  <html> and the search input
- Manga detail CTA localized to 开始阅读

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:17:54 +08:00

74 lines
1.9 KiB
TypeScript

import { notFound } from "next/navigation";
import { prisma } from "@/lib/db";
import { PageReader } from "@/components/PageReader";
import type { Metadata } from "next";
type Props = {
params: Promise<{ slug: string; chapter: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug, chapter } = await params;
const chapterNum = parseInt(chapter, 10);
if (isNaN(chapterNum)) return { title: "Not Found" };
const manga = await prisma.manga.findUnique({ where: { slug } });
if (!manga) return { title: "Not Found" };
return {
title: `${manga.title} — Ch. ${chapterNum}`,
description: `Read chapter ${chapterNum} of ${manga.title}`,
};
}
export default async function ChapterReaderPage({ params }: Props) {
const { slug, chapter } = await params;
const chapterNum = parseInt(chapter, 10);
if (isNaN(chapterNum)) notFound();
const manga = await prisma.manga.findUnique({
where: { slug },
include: {
chapters: {
orderBy: { number: "asc" },
include: {
_count: { select: { pages: true } },
},
},
},
});
if (!manga) notFound();
const currentChapter = manga.chapters.find((c) => c.number === chapterNum);
if (!currentChapter) notFound();
const chapterIndex = manga.chapters.findIndex(
(c) => c.number === chapterNum
);
const prevChapter =
chapterIndex > 0 ? manga.chapters[chapterIndex - 1].number : null;
const nextChapter =
chapterIndex < manga.chapters.length - 1
? manga.chapters[chapterIndex + 1].number
: null;
const allChapters = manga.chapters.map((c) => ({
id: c.id,
number: c.number,
title: c.title,
totalPages: c._count.pages,
}));
return (
<PageReader
mangaSlug={manga.slug}
mangaTitle={manga.title}
startChapterNumber={currentChapter.number}
prevChapter={prevChapter}
nextChapter={nextChapter}
chapters={allChapters}
/>
);
}