Compare commits
No commits in common. "f2ef775f7095dc2b107b576cd4053593e89dd887" and "6c750e70ce3f3737bffd6446508d380d15b34f42" have entirely different histories.
f2ef775f70
...
6c750e70ce
163
AGENTS.md
Normal file
163
AGENTS.md
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# This is NOT the Next.js you know
|
||||||
|
|
||||||
|
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||||
|
|
||||||
|
# Manga Website Project Requirements
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|-------|-----------|
|
||||||
|
| Frontend & Backend | Next.js (App Router, TypeScript, Tailwind CSS) |
|
||||||
|
| Database | PostgreSQL 16 |
|
||||||
|
| ORM | Prisma |
|
||||||
|
| Image Storage | Cloudflare R2 |
|
||||||
|
| Containerization | Docker + Docker Compose (managed via Portainer) |
|
||||||
|
| Reverse Proxy | Nginx via aaPanel |
|
||||||
|
| Domain | `manga.04080616.xyz` |
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
- **Server**: Proxmox host, Docker VM/LXC running Portainer
|
||||||
|
- **Reverse proxy**: aaPanel Nginx routes `manga.04080616.xyz` → `http://127.0.0.1:3001`
|
||||||
|
- **SSL**: Let's Encrypt via aaPanel
|
||||||
|
- **DDNS already configured** — no Cloudflare Tunnel needed
|
||||||
|
|
||||||
|
### Port Allocation
|
||||||
|
|
||||||
|
| Port | Service |
|
||||||
|
|------|---------|
|
||||||
|
| 3001 | Next.js app (host) → 3000 (container) |
|
||||||
|
| 5433 | PostgreSQL (host) → 5432 (container) |
|
||||||
|
|
||||||
|
Ports 3000, 8000–8002, 8005, 2283, 5432, 51820–51821, 9443 are already in use.
|
||||||
|
|
||||||
|
## Deployment Workflow
|
||||||
|
|
||||||
|
1. Develop on macOS locally
|
||||||
|
2. Push code to Gitea (`gitea.04080616.xyz/yiekheng/sunnymh-manga-site`)
|
||||||
|
3. On Proxmox Docker host: `git pull` → `docker build` → restart container
|
||||||
|
4. Portainer used for container management (GUI)
|
||||||
|
|
||||||
|
## Image Storage (Cloudflare R2)
|
||||||
|
|
||||||
|
- Images stored in R2 (S3-compatible), **not** on the Proxmox host
|
||||||
|
- Upload flow: backend generates presigned URL → browser uploads directly to R2 → database records image path
|
||||||
|
- Format: **WebP** (25–35% smaller than JPEG)
|
||||||
|
- Multiple resolutions: thumbnail / reading / original
|
||||||
|
|
||||||
|
### R2 Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/manga/{manga_id}/{chapter_id}/{page_number}.webp
|
||||||
|
/thumbnails/{manga_id}/cover.webp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Schema (Prisma)
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model Manga {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
title String
|
||||||
|
description String
|
||||||
|
coverUrl String
|
||||||
|
slug String @unique
|
||||||
|
status Status @default(PUBLISHED)
|
||||||
|
chapters Chapter[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Chapter {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
mangaId Int
|
||||||
|
number Int
|
||||||
|
title String
|
||||||
|
pages Page[]
|
||||||
|
manga Manga @relation(fields: [mangaId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Page {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
chapterId Int
|
||||||
|
number Int
|
||||||
|
imageUrl String
|
||||||
|
chapter Chapter @relation(fields: [chapterId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
PUBLISHED
|
||||||
|
DRAFT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Core (build first)
|
||||||
|
- Manga listing page with cover images
|
||||||
|
- Manga detail page (title, description, chapter list)
|
||||||
|
- Chapter reader page (page-by-page image display)
|
||||||
|
- Internal search by manga title (PostgreSQL `contains` query)
|
||||||
|
|
||||||
|
### SEO
|
||||||
|
- Next.js SSR/SSG for all public pages
|
||||||
|
- `generateMetadata()` per page (title, description)
|
||||||
|
- Auto-generated `/sitemap.xml` via `app/sitemap.ts`
|
||||||
|
- Submit sitemap to Google Search Console
|
||||||
|
|
||||||
|
### Search
|
||||||
|
- **Phase 1**: PostgreSQL `contains` with `insensitive` mode (sufficient for < 10k titles)
|
||||||
|
- **Phase 2** (future): Meilisearch for fuzzy/full-text search if needed
|
||||||
|
|
||||||
|
## URL Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/ Homepage (manga grid)
|
||||||
|
/manga/[slug] Manga detail + chapter list
|
||||||
|
/manga/[slug]/[chapter] Chapter reader
|
||||||
|
/api/search?q= Search API endpoint
|
||||||
|
/api/manga Manga CRUD (admin)
|
||||||
|
/api/upload R2 presigned URL generation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables (.env)
|
||||||
|
|
||||||
|
```env
|
||||||
|
DATABASE_URL=postgresql://manga_user:yourpassword@localhost:5433/manga_db
|
||||||
|
|
||||||
|
R2_ACCOUNT_ID=your_cloudflare_account_id
|
||||||
|
R2_ACCESS_KEY=your_r2_access_key
|
||||||
|
R2_SECRET_KEY=your_r2_secret_key
|
||||||
|
R2_BUCKET=manga-images
|
||||||
|
R2_PUBLIC_URL=https://your-r2-public-url.r2.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nginx Reverse Proxy Config (aaPanel)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content Source
|
||||||
|
|
||||||
|
- **Public domain / free manga only** (no copyright issues)
|
||||||
|
- Sources: Comic Book Plus, Digital Comic Museum, Internet Archive
|
||||||
|
- Initial: manual download and upload
|
||||||
|
- Future: automated scraper (Node.js + cheerio) for allowed sources
|
||||||
|
|
||||||
|
## Recommended Build Order
|
||||||
|
|
||||||
|
1. Set up Prisma schema + connect to PostgreSQL
|
||||||
|
2. Build manga listing and reader pages (static UI first)
|
||||||
|
3. Wire up database queries via Prisma
|
||||||
|
4. Add R2 upload + image serving
|
||||||
|
5. Add search API
|
||||||
|
6. Add SEO metadata + sitemap
|
||||||
|
7. Dockerize and deploy to Proxmox via Gitea
|
||||||
190
CLAUDE.md
190
CLAUDE.md
@ -2,181 +2,67 @@
|
|||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
> **This is NOT the Next.js you know.** Next 16 / App Router has breaking
|
@AGENTS.md
|
||||||
> changes from older training data — APIs, conventions, and file structure
|
|
||||||
> may differ. Check `node_modules/next/dist/docs/` before writing routing
|
|
||||||
> or metadata code, and heed deprecation notices.
|
|
||||||
|
|
||||||
## Build & Development Commands
|
## Build & Development Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev # Start dev server (hot reload, Turbopack)
|
npm run dev # Start dev server (hot reload)
|
||||||
npm run build # Production build
|
npm run build # Production build
|
||||||
npm start # Start production server
|
npm start # Start production server
|
||||||
npm run lint # ESLint (flat config, v9+)
|
npm run lint # ESLint (flat config, v9+)
|
||||||
npx prisma generate # Regenerate Prisma client after schema changes
|
npx prisma generate # Regenerate Prisma client after schema changes
|
||||||
npx prisma db push # Sync schema to DB (this repo uses db push, not migrate)
|
npx prisma migrate dev --name <name> # Create and apply a migration
|
||||||
npx tsx scripts/backfill-page-dims.ts # Probe Page.width/height via image-size against R2
|
npx prisma db push # Push schema changes without migration (dev only)
|
||||||
```
|
```
|
||||||
|
|
||||||
The DB is managed via `prisma db push`, not `migrate dev`. `prisma/migrations/`
|
|
||||||
holds SQL files kept for reference only; `migrate dev` won't work (no shadow
|
|
||||||
DB permission on this Postgres).
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
**Stack**: Next.js 16.2.1 (App Router) · React 19 · TypeScript · Tailwind CSS v4 · Prisma · PostgreSQL 16 · Cloudflare R2
|
**Stack**: Next.js 16.2.1 (App Router) · React 19 · TypeScript · Tailwind CSS v4 · Prisma · PostgreSQL 16 · Cloudflare R2
|
||||||
|
|
||||||
**Path alias**: `@/*` maps to project root.
|
**Path alias**: `@/*` maps to project root (configured in tsconfig.json).
|
||||||
|
|
||||||
### Reader is placeholder-based — read this before touching `PageReader.tsx`
|
### App Router Structure
|
||||||
|
|
||||||
`components/PageReader.tsx` is deliberately **not** a naive `pages.map(<img>)`.
|
```
|
||||||
Every page renders as a `<div>` placeholder with `style={{ aspectRatio: w/h }}`
|
app/
|
||||||
so the document height is correct before any image bytes load. This avoids
|
├── page.tsx # Homepage (manga grid)
|
||||||
a whole class of iOS Safari scroll-anchoring / prepend-shift bugs that a
|
├── layout.tsx # Root layout (Geist fonts, metadata)
|
||||||
simple reader would hit. Key invariants:
|
├── manga/[slug]/page.tsx # Manga detail + chapter list
|
||||||
|
├── manga/[slug]/[chapter]/page.tsx # Chapter reader
|
||||||
|
├── api/search/route.ts # Search endpoint (?q=)
|
||||||
|
├── api/manga/route.ts # Manga CRUD (admin)
|
||||||
|
├── api/upload/route.ts # R2 presigned URL generation
|
||||||
|
└── sitemap.ts # Auto-generated sitemap
|
||||||
|
```
|
||||||
|
|
||||||
- `Page.width` / `Page.height` **must** be populated for every row. The
|
### Data Flow
|
||||||
Python scraper writes them on insert; the backfill script covers older
|
|
||||||
rows. With dims=0, the reader falls back to 3/4 and layout shifts on
|
|
||||||
image load.
|
|
||||||
- Initial chapter meta (dims skeleton) is fetched server-side in
|
|
||||||
`app/manga/[slug]/[chapter]/page.tsx` and passed as a prop. Later
|
|
||||||
chapters' meta is lazy-fetched via `/api/chapters/[id]/meta`.
|
|
||||||
- Image URLs (signed, short-TTL) come in batches via `/api/pages` triggered
|
|
||||||
by an `IntersectionObserver` with 1200 px margin. A separate
|
|
||||||
intersecting-pages `Map` is maintained for the scroll tick so the topmost
|
|
||||||
visible page can be found in O(k) without walking every placeholder.
|
|
||||||
- Continuous multi-chapter reading auto-appends the next chapter when the
|
|
||||||
user gets within `PREFETCH_NEXT_AT = 3` pages of the end.
|
|
||||||
- URL syncs to the chapter currently in viewport via
|
|
||||||
`window.history.replaceState`. `prevChapter` / `nextChapter` for
|
|
||||||
double-tap nav are derived from `currentChapterNum`, **not** from the URL
|
|
||||||
or props.
|
|
||||||
- All Links into the reader use `scroll={false}`, and the double-tap
|
|
||||||
`router.push(..., { scroll: false })`. This is load-bearing — without it,
|
|
||||||
App Router's default scroll-to-top clobbers the `useLayoutEffect` that
|
|
||||||
restores the resume page.
|
|
||||||
- Reading progress is persisted to `localStorage` under the key
|
|
||||||
`sunnymh:last-read:<slug>` as JSON `{chapter, page}`. `readProgress`
|
|
||||||
in `components/ReadingProgressButton.tsx` also accepts the **legacy
|
|
||||||
bare-number format** (just a chapter number string) for backward
|
|
||||||
compatibility — preserve this when touching the storage format.
|
|
||||||
|
|
||||||
### Immersive reader route
|
- **Database**: Prisma ORM → PostgreSQL. Models: `Manga`, `Chapter`, `Page` (see `prisma/schema.prisma`).
|
||||||
|
- **Images**: Stored in Cloudflare R2 (S3-compatible). Upload flow: backend generates presigned URL → browser uploads directly to R2 → database records the image path.
|
||||||
|
- **R2 bucket layout**: `/manga/{manga_id}/{chapter_id}/{page_number}.webp` and `/thumbnails/{manga_id}/cover.webp`.
|
||||||
|
- **Search**: PostgreSQL `contains` with `insensitive` mode (phase 1). No external search engine needed until >10k titles.
|
||||||
|
|
||||||
`components/Header.tsx` and `components/BottomNav.tsx` both return `null`
|
### Key Libraries
|
||||||
when the pathname matches `/manga/[slug]/[chapter]` — the reader runs
|
|
||||||
full-bleed with its own chrome. Anything new that relies on the global
|
|
||||||
nav being present needs to account for this.
|
|
||||||
|
|
||||||
### Data flow
|
- `@aws-sdk/client-s3` + `@aws-sdk/s3-request-presigner` — R2 uploads (S3-compatible API)
|
||||||
|
- `prisma` / `@prisma/client` — ORM and database client
|
||||||
- **Prisma models** (see `prisma/schema.prisma`): `Manga`, `Chapter`, `Page`.
|
|
||||||
`Page` carries `width` / `height`. `Manga` has `genre` as a
|
|
||||||
comma-separated string; parse with `lib/genres.ts`.
|
|
||||||
- **Images live in R2** (S3-compatible). Format is **WebP** (~25–35%
|
|
||||||
smaller than JPEG). The app **never** serves R2 URLs directly —
|
|
||||||
`lib/r2.ts::signUrl` / `signCoverUrls` mint presigned GETs with a 60 s
|
|
||||||
TTL; `keyFromPublicUrl` reverses a public URL to its R2 key.
|
|
||||||
`signCoverUrls` is the standardized pattern for list pages — homepage
|
|
||||||
carousel, genre grid, search results, detail page all route through it.
|
|
||||||
Don't bypass it by handing raw `manga.coverUrl` to the browser.
|
|
||||||
- **R2 layout** (populated by the scraper — not this app):
|
|
||||||
- `manga/<slug>/chapters/<chapter_number>/<page_number>.webp`
|
|
||||||
- `manga/<slug>/cover.webp`
|
|
||||||
- **Search**: PostgreSQL `contains` + `mode: 'insensitive'`. Fine up to
|
|
||||||
~10k titles. If a future scale requires fuzzy/full-text, the planned
|
|
||||||
swap-in is Meilisearch.
|
|
||||||
|
|
||||||
### Key routes
|
|
||||||
|
|
||||||
- `app/page.tsx` — homepage (trending carousel + genre tabs).
|
|
||||||
- `app/manga/[slug]/page.tsx` — detail + chapter list + "continue reading".
|
|
||||||
- `app/manga/[slug]/[chapter]/page.tsx` — reader. Issues both required
|
|
||||||
queries (manga+chapters, initial chapter page meta) in parallel via
|
|
||||||
`Promise.all`.
|
|
||||||
- `app/api/pages/route.ts` — batched `{number, imageUrl}` with signed URLs.
|
|
||||||
- `app/api/chapters/[chapterId]/meta/route.ts` — `[{number, width, height}]`
|
|
||||||
for lazy next-chapter prefetch.
|
|
||||||
- `app/api/search/route.ts` — case-insensitive title search.
|
|
||||||
- `app/api/upload/route.ts` — R2 presigned PUT (admin/scraper only; no DB
|
|
||||||
write happens here).
|
|
||||||
- `app/sitemap.ts` — auto-generated `/sitemap.xml`. Each page uses
|
|
||||||
`generateMetadata()` for per-route title/description.
|
|
||||||
|
|
||||||
### Ingestion is a sibling Python repo
|
|
||||||
|
|
||||||
New manga and chapters are created by `../manga-dl/manga.py`, not by this
|
|
||||||
web app. It scrapes, converts to WebP, uploads to R2, and writes
|
|
||||||
`Manga` / `Chapter` / `Page` rows directly via `psycopg2`. When updating the
|
|
||||||
Prisma schema, update the sibling's SQL INSERTs too (they're raw, not ORM).
|
|
||||||
|
|
||||||
### Branding
|
|
||||||
|
|
||||||
- Source SVGs in `logo/qingtian_*.svg`.
|
|
||||||
- Wired up as: `app/icon.svg` (browser favicon, Next.js convention),
|
|
||||||
`app/apple-icon.svg` (iOS home-screen), `public/logo.svg` (Header `<img>`).
|
|
||||||
- Accent color: `#268a52` (green sunflower), defined as `--accent` in
|
|
||||||
`app/globals.css`. Hover: `#1f7044`.
|
|
||||||
- Brand name: 晴天漫画 · Sunny MH.
|
|
||||||
|
|
||||||
### iOS Safari conventions
|
|
||||||
|
|
||||||
- `viewport` in `app/layout.tsx` sets `viewportFit: "cover"` so the app
|
|
||||||
extends into the notch; any sticky/fixed chrome must use the
|
|
||||||
`pt-safe` / `pb-safe` utilities from `app/globals.css` (they map to
|
|
||||||
`env(safe-area-inset-*)`). Header and BottomNav already do.
|
|
||||||
- Horizontal carousels (`components/TrendingCarousel.tsx`) set
|
|
||||||
`WebkitOverflowScrolling: "touch"` inline for momentum scrolling.
|
|
||||||
Preserve this on any new swipe lane.
|
|
||||||
- `overflow-x: hidden` on `body` (globals.css) prevents the fixed
|
|
||||||
BottomNav from leaking horizontal scroll on mobile.
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
- **Host**: Proxmox VM running Docker + Portainer.
|
- **Docker**: `docker build` from root Dockerfile → container exposes port 3000, mapped to host port 3001.
|
||||||
- **Flow**: Push to Gitea (`gitea.04080616.xyz/yiekheng/sunnymh-manga-site`)
|
- **Compose**: `docker-compose.yml` runs both the app and PostgreSQL 16.
|
||||||
→ `git pull` on the Docker host → rebuild → restart via Portainer.
|
- **Flow**: Push to Gitea → `git pull` on Proxmox Docker host → rebuild → restart via Portainer.
|
||||||
- **Dockerfile** at repo root; container listens on 3000, mapped to host
|
- **Reverse proxy**: aaPanel Nginx routes `manga.04080616.xyz` → `http://127.0.0.1:3001`.
|
||||||
3001. `docker-compose.yml` only brings up the app — **Postgres is
|
- **Port 3001** (app) and **5433** (Postgres) on the host — many other ports are taken.
|
||||||
external** (connected via `DATABASE_URL`), not defined in compose.
|
|
||||||
- **Reverse proxy**: aaPanel Nginx routes `www.04080616.xyz` →
|
|
||||||
`http://127.0.0.1:3001`. SSL via Let's Encrypt managed in aaPanel. DDNS
|
|
||||||
already configured — no Cloudflare Tunnel involved.
|
|
||||||
- **Host ports in use elsewhere**: 3000, 8000–8002, 8005, 2283, 5432,
|
|
||||||
51820–51821, 9443 — don't collide. Postgres lives on host 5433.
|
|
||||||
- After schema changes reach prod, run `scripts/backfill-page-dims.ts`
|
|
||||||
once against prod DB to populate dims for historical rows.
|
|
||||||
|
|
||||||
### Minimal Nginx location block (aaPanel)
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
location / {
|
|
||||||
proxy_pass http://127.0.0.1:3001;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
Required in `.env` (container equivalents in `docker-compose.yml`):
|
Required in `.env` (see docker-compose.yml for container equivalents):
|
||||||
|
|
||||||
- `DATABASE_URL` — PostgreSQL connection string, e.g. `postgresql://manga_user:…@localhost:5433/manga_db`
|
- `DATABASE_URL` — PostgreSQL connection string
|
||||||
- `R2_ACCOUNT_ID`, `R2_ACCESS_KEY`, `R2_SECRET_KEY`, `R2_BUCKET`, `R2_PUBLIC_URL` — Cloudflare R2
|
- `R2_ACCOUNT_ID`, `R2_ACCESS_KEY`, `R2_SECRET_KEY`, `R2_BUCKET`, `R2_PUBLIC_URL` — Cloudflare R2
|
||||||
|
|
||||||
## Dev gotchas
|
## Content Policy
|
||||||
|
|
||||||
- **`next.config.ts` has `allowedDevOrigins: ["10.8.0.2"]`** — a VPN-access
|
All manga content must be **public domain or free** (Comic Book Plus, Digital Comic Museum, Internet Archive). No copyrighted material.
|
||||||
whitelist so dev on macOS is reachable from other devices on the tunnel.
|
|
||||||
Removing or breaking it silently blocks hot-reload from those origins;
|
|
||||||
update it if the VPN subnet changes.
|
|
||||||
- **`prisma/seed.ts` is documentation-only.** The real data pipeline is
|
|
||||||
the sibling Python scraper (`../manga-dl/manga.py`) writing directly via
|
|
||||||
`psycopg2`. The `npm run seed` script exists for quick local dev but is
|
|
||||||
never run in production.
|
|
||||||
|
|||||||
@ -373,7 +373,6 @@ export function PageReader({
|
|||||||
<div className="flex items-center gap-3 px-4 h-12 max-w-4xl mx-auto">
|
<div className="flex items-center gap-3 px-4 h-12 max-w-4xl mx-auto">
|
||||||
<Link
|
<Link
|
||||||
href={`/manga/${mangaSlug}`}
|
href={`/manga/${mangaSlug}`}
|
||||||
scroll={false}
|
|
||||||
className="text-foreground/80 hover:text-foreground transition-colors shrink-0"
|
className="text-foreground/80 hover:text-foreground transition-colors shrink-0"
|
||||||
aria-label="Back to manga"
|
aria-label="Back to manga"
|
||||||
>
|
>
|
||||||
@ -477,7 +476,6 @@ export function PageReader({
|
|||||||
<p className="text-base font-semibold">{mangaTitle}</p>
|
<p className="text-base font-semibold">{mangaTitle}</p>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
scroll={false}
|
|
||||||
className="px-5 py-2 rounded-lg bg-accent text-white text-sm font-semibold transition-colors hover:bg-accent-hover"
|
className="px-5 py-2 rounded-lg bg-accent text-white text-sm font-semibold transition-colors hover:bg-accent-hover"
|
||||||
>
|
>
|
||||||
Back to Home
|
Back to Home
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user