163 Commits
Author SHA1 Message Date
gosangamandClaude Opus 5.5 f855699e72 Quizzes: To do = started but unfinished; durations roll into days
The To do tab now lists only quizzes you've started and not finished;
untouched ones stay under All. Quiz times show hours and days
(3h 20m, 2d 4h) instead of ever-growing minutes, and 59.96s no longer
prints as "60.0s".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:29:05 +05:30
gosangamandClaude Opus 5.5 52cd48b12e Feedback form: load categories from the BFF
The form offers the admin-managed categories, falling back to the original
four if they can't be loaded. "My feedback" shows the server's labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 13:47:09 +05:30
gosangamandClaude Opus 5.5 0a23ab59f7 Quiz list: drill down by Books → book and Lectures → year
The All tab is now a click-through of category cards — Books, Lectures &
Conversations, Other quizzes — each opening its books or years, then the
quizzes themselves. Only categories with quizzes get a card. The position
and tab live in the URL (?in=books&book=bg, ?tab=todo), so refresh, Back
and shared links keep your place. Book quizzes list in chapter order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 13:23:46 +05:30
gosangam 3af4e1bcd3 quiz grouping 2026-10-18 13:02:30 +05:30
gosangam fed12b0d46 ui fix 2026-10-17 02:41:32 +05:30
gosangam d64485daa4 remove confetti 2026-10-16 20:04:25 +05:30
gosangam 0968b6046d feat: quiz confetti 2026-10-16 15:27:46 +05:30
gosangam 8dd72de359 quiz link 2026-10-16 03:25:08 +05:30
gosangam 8537eb6ce2 audio player respects group context 2026-10-15 23:18:05 +05:30
gosangam 09dc734613 mobile responsive 2026-10-15 22:29:17 +05:30
gosangam 5832125ab2 quiz btn styling update 2026-10-15 22:14:34 +05:30
gosangam 0e2e0d7300 quiz seo image and authguard 2026-10-15 21:09:37 +05:30
gosangam 6a6143ceb2 feat: quiz 2026-10-15 20:07:01 +05:30
gosangam 9f67da37a6 feedback formatter 2026-10-09 06:40:28 +05:30
gosangam cce8a4c05f share to chat fix 2026-09-02 00:15:31 +05:30
gosangam b391e1c575 fix: about us page in legal 2026-08-30 11:16:33 +05:30
gosangam 3ce08428c8 fix: notification matrix 2026-08-30 11:15:54 +05:30
gosangam bf8db28a7c feat: chat search 2026-08-30 10:57:28 +05:30
gosangam cc9ea45c61 feat: sticker preview 2026-08-30 10:54:56 +05:30
gosangam 7a9d362b15 fix: chat UI fix for media 2026-08-30 03:12:43 +05:30
gosangam c001eeab45 fix: stickers and chat UI fix 2026-08-30 03:01:20 +05:30
gosangam 2b0f966f90 fix: chat bubble text unselectable 2026-08-30 02:28:31 +05:30
gosangam 2597c6f2ec feat: chat stickers 2026-08-30 02:19:31 +05:30
gosangam 8c7afe596d feat: chat reactions 2026-08-30 01:51:52 +05:30
gosangam 0f382ed885 fix: no attachment broadcast fix 2026-08-29 18:56:45 +05:30
gosangam 03c8799f22 feat: broadcast 2026-08-29 18:34:21 +05:30
gosangam 011b5bba91 fix: chat UI 2026-08-29 17:30:32 +05:30
gosangam 3e6a1c6f39 fix: chat accepts posts 2026-08-29 16:26:53 +05:30
gosangam 76cd106ef7 fix: chat accepts quotes now 2026-08-29 11:58:33 +05:30
gosangam 028a8d7e1c fix: chat UI fixes 2026-08-29 04:12:30 +05:30
gosangam 290ceb048f fix: chat URL fix in bottom nav 2026-08-29 04:02:07 +05:30
gosangam 523b6fa7e8 feat: chat 2026-08-29 03:49:58 +05:30
gosangam 3257efb06a feat: private r2 bucket and secure sockets 2026-08-29 01:21:25 +05:30
gosangam 6083a485de fix: carousel image lightbox 2026-08-09 12:40:50 +05:30
gosangam baf36d3d67 fix: carousel image lightbox 2026-08-09 11:11:35 +05:30
gosangam df49e7afcc fix: new user scroll fix, add image lightbox 2026-08-09 10:58:49 +05:30
gosangam ce373e3e03 fix: profile bio 2026-08-08 00:49:39 +05:30
gosangam 9b7758da99 fix: profile bio 2026-08-08 00:12:34 +05:30
gosangam 6ff29db68a feat: profile bio 2026-08-07 23:47:22 +05:30
gosangam 274c735b4d feat: disciple highlight 2026-08-07 22:47:04 +05:30
gosangam 1984797ad3 feat: auth user feedbacks 2026-07-14 18:26:20 +05:30
gosangam 1582df0ed5 chore: refactor settings route 2026-07-14 18:16:32 +05:30
gosangam 1683abc2e9 fix: offline-content renamed to downloads 2026-07-14 16:51:17 +05:30
gosangam 12a5c15121 feat: add daily vani 2026-07-11 00:57:35 +05:30
gosangam 8bc27bb8a1 feat: add daily vani 2026-07-11 00:45:51 +05:30
gosangam 89391279bb feat: add daily vani 2026-07-11 00:31:37 +05:30
gosangam 7f716397a0 feat: kautilya URL update 2026-07-03 13:32:08 +05:30
gosangam 4586f1c22c fix: offline content layout fix 2026-07-02 23:25:40 +05:30
gosangam b6471f9430 fix: offline content for iOS 2026-07-02 21:52:35 +05:30
gosangam 380c23e469 feat: offline content 2026-07-02 21:32:22 +05:30
gosangam 0b67a1142f feat: audio track history 2026-07-02 19:23:22 +05:30
gosangam be28585294 fix: comments drawer fix 2026-07-02 18:57:24 +05:30
gosangam 1462844e95 feat: books sequence update 2026-07-02 17:49:17 +05:30
gosangam 9916810d18 fix: bg reference purport update 2026-06-23 15:13:27 +05:30
gosangam 7b81bbc791 feat: enhance user limits 2026-06-23 14:21:59 +05:30
gosangam f92b41384e feat: add replace_hero hook to custom section 2026-06-21 23:36:25 +05:30
gosangam 5cd75c27f9 fix: user post feed view 2026-06-21 20:00:14 +05:30
gosangam 5679f7a639 fix: user post feed view 2026-06-21 18:35:48 +05:30
gosangam 25db9d0eee feat: add friend request tab in notifications 2026-06-18 22:45:40 +05:30
gosangam 0178f89f2b test: rybbit proxy ip 2026-06-18 01:09:09 +05:30
gosangam b6dc762984 fix: rybbit proxy fix 2026-06-18 00:58:36 +05:30
gosangam 1fe10adb1e fix: rybbit proxy fix 2026-06-18 00:51:09 +05:30
gosangam 84cb273941 fix: feed card follow ui 2026-06-18 00:21:06 +05:30
gosangam 071eb4d58a fix: book transcript browser navigation 2026-06-17 18:00:13 +05:30
gosangam bc1798b31b fix: ui fixes in transcript listing for books 2026-06-17 14:14:45 +05:30
gosangam fc2aef0c3d fix: ui fixes in transcript lusting for books 2026-06-17 14:02:06 +05:30
gosangam 89f94c1a6e fix: ui fixes in transcript nav 2026-06-17 13:48:59 +05:30
gosangam f5abe33c09 feat: add book transcripts categorisation 2026-06-17 12:40:40 +05:30
gosangam f96e7dd2f1 feat: admin notifications 2026-06-16 14:48:29 +05:30
gosangam 0cf6df5942 fix: ui fix 2026-06-16 01:30:41 +05:30
gosangam 5c72416ae8 fix: hidden post 2026-06-16 00:05:07 +05:30
gosangam 197e13c0ef feat: add edit profile page 2026-06-15 23:54:34 +05:30
gosangam 0608e2b6f6 fix: ui fix 2026-06-15 17:15:38 +05:30
gosangam cb376f8497 fix: ui fix 2026-06-15 15:24:49 +05:30
gosangam eac56b7974 fix(seo): fix sitemap URL 2026-06-15 14:04:00 +05:30
gosangam be4c2f17fe feat(seo): add sitemap generator 2026-06-15 13:52:12 +05:30
gosangam 6cc9285bad fix: ui fixes 2026-06-15 12:17:39 +05:30
gosangam ab35767374 fix: ui fixes 2026-06-15 10:13:00 +05:30
gosangam c551d2f7a8 fix: ui fixes 2026-06-15 10:04:11 +05:30
gosangam a4fd63695a fix: ui fixes 2026-06-15 09:53:01 +05:30
gosangam 91621693a6 feat: hide post 2026-06-14 20:11:04 +05:30
gosangam 79f0bd9223 feat: hide post 2026-06-14 19:52:16 +05:30
gosangam 7585e6cb31 fix: ui fixes 2026-06-14 18:04:11 +05:30
gosangam c8de98c8de fix: ui fixes 2026-06-14 16:11:56 +05:30
gosangam 72a092b3c9 fix: ui fixes 2026-06-14 16:06:00 +05:30
gosangam 752ad20fed fix: ui fixes 2026-06-14 15:56:58 +05:30
gosangam 0280a81777 feat: add sb front matter 2026-06-14 15:32:33 +05:30
gosangam 69e5f2c45c feat: add sb front matter 2026-06-14 14:49:25 +05:30
gosangam 45c7cf047e feat: add cc front matter 2026-06-14 13:57:38 +05:30
gosangam a4baf6d215 feat: add bg front matter 2026-06-14 13:28:29 +05:30
gosangam 455ed0f7cb fix: copy behaviour 2026-06-14 12:51:15 +05:30
gosangam 7c3cf656b5 feat: add search in page for books, magazines and other docs 2026-06-14 02:59:42 +05:30
gosangam 23d06a3db5 fix: og image fix for collections 2026-06-14 01:49:44 +05:30
gosangam 3accc753d0 feat: share collection URL 2026-06-14 01:29:08 +05:30
gosangam b04ed1d3aa fix: fix feed component size 2026-06-14 00:43:33 +05:30
gosangam 9808dd3033 fix: fix feed component 2026-06-14 00:35:45 +05:30
gosangam b248f962a1 fix: fix feed component 2026-06-14 00:30:37 +05:30
gosangam 61581b67a7 feat: add feed 2026-06-14 00:18:31 +05:30
gosangam eb0c981d3c feat: add inline mentions in comments 2026-06-13 13:10:34 +05:30
gosangam 37305201bd fix: delete comment modal 2026-06-13 03:15:51 +05:30
gosangam 4471ef10aa fix: delete comment modal 2026-06-13 03:04:46 +05:30
gosangam 397a09e814 fix: ui fixes in emoji picker 2026-06-13 02:53:25 +05:30
gosangam 9e68184750 fix: ui fixes 2026-06-13 02:42:07 +05:30
gosangam 439664a97d chore: code refactor 2026-06-13 02:24:42 +05:30
gosangam cc5633c37a feat: add comments 2026-06-13 01:30:18 +05:30
gosangam 6a0559e608 fix: hide blocked user content 2026-06-13 00:47:11 +05:30
gosangam 25a729959f chore: theme update 2026-06-11 16:14:53 +05:30
gosangam af5ab9cf6b fix: copy clipboard fix 2026-06-11 11:47:31 +05:30
gosangam 884a984e50 fix: search in transcript page ui fixes 2026-06-11 00:39:39 +05:30
gosangam ead8ad02e8 fix: ui fixes 2026-06-11 00:32:04 +05:30
gosangam 4b159cb4d9 fix: drawer fix 2026-06-10 20:02:23 +05:30
gosangam 3903423d06 fix: mention input zoom behaviour 2026-06-10 19:21:32 +05:30
gosangam a46e29d998 fix: mention input zoom behaviour 2026-06-10 16:56:20 +05:30
gosangam 922d76e21f fix: mention user position in post compose drawer 2026-06-10 16:52:21 +05:30
gosangam 909ae67320 feat: mention 2026-06-10 16:29:44 +05:30
gosangam 482a845f37 feat: add email notif for post like 2026-06-09 16:57:37 +05:30
gosangam 647a803b96 feat: add post like 2026-06-09 16:34:10 +05:30
gosangam e36afdd1a5 feat: add followers/following list 2026-06-09 13:20:44 +05:30
gosangam ef6e280524 fix: sync sadhna calendar with local timezone 2026-06-09 00:39:12 +05:30
gosangam cf733062f8 feat: copy caption in post actions menu 2026-06-08 23:45:01 +05:30
gosangam 421b41cade fix: push notification 2026-06-08 17:52:27 +05:30
gosangam deeda40c9d fix: push notification 2026-06-08 17:17:50 +05:30
gosangam 90df5bc784 fix: webpush for iOS 2026-06-08 16:09:09 +05:30
gosangam 0334e03c3c feat: add webpush and sadhna tracker 2026-06-08 15:48:12 +05:30
gosangam ecb32d2e85 wip: sadhna streak 2026-06-07 22:32:34 +05:30
gosangam d5fb350652 fix: rybbit blocking using next rewrite 2026-06-07 00:52:49 +05:30
gosangam bcffe9684f feat: add websocket and analytics 2026-06-07 00:23:54 +05:30
gosangam 0db339254d fix: text selection 2026-06-05 14:13:13 +05:30
gosangam 684ea2ae97 feat: social graph features 2026-06-04 19:30:50 +05:30
gosangam 0f433c5708 fix: audio player 2026-06-03 23:01:33 +05:30
gosangam 48dd450a15 fix: post reshare and audio player enhancements 2026-06-03 14:41:34 +05:30
gosangam ddc6d3c7bf feat: avatar opener 2026-06-03 00:49:58 +05:30
gosangam fd333159f6 fix: font size revert 2026-06-02 23:51:42 +05:30
gosangam 0bcda5a791 fix: ui fixes minor 2026-06-02 23:14:34 +05:30
gosangam c89cc8b779 fix: tailwind fix 2026-06-02 22:57:22 +05:30
gosangam ec67ddf852 feat: social auth account linking 2026-06-02 22:55:09 +05:30
gosangam 086f1dd8aa fix: type fix 2026-06-02 19:38:23 +05:30
gosangam b61c3df473 feat: add google auth 2026-06-02 18:44:43 +05:30
gosangam cb70a84dd5 feat: add password reset email 2026-06-02 18:37:38 +05:30
gosangam dc01d09f09 login with facebook added 2026-06-02 15:21:47 +05:30
gosangam 84d86c45b5 post cards overlay enhancements 2026-06-02 15:13:02 +05:30
gosangam 4d45e20cb1 post cards overlay enhancements 2026-06-02 15:02:17 +05:30
gosangam e72679d1b6 oauth hidden 2026-06-02 12:20:50 +05:30
gosangam 91d5d272d2 feat: add policy pages 2026-06-02 01:57:08 +05:30
gosangam 0352cb2003 search target fix 2026-06-02 00:42:26 +05:30
gosangam 8a8ce24479 fix: reshared posts should be shown in feed 2026-05-31 14:54:48 +05:30
gosangam 7d76afe102 collection drawer ui fix 2026-05-31 11:41:18 +05:30
gosangam 8c1f3a1b7b ui fixes in bottom nav 2026-05-31 11:23:55 +05:30
gosangam 3624c19d2d search page ui fixes 2026-05-31 11:19:35 +05:30
gosangam 5988d02373 search page ui fixes 2026-05-31 11:16:50 +05:30
gosangam 17fb83087f search page ui fixes 2026-05-31 11:09:43 +05:30
gosangam 489adb0eeb feat: send icon fix 2026-05-31 10:53:21 +05:30
gosangam d8de0a18e5 feat: add user search 2026-05-31 10:43:30 +05:30
gosangam c20d6bffe8 feat: share sheet update 2026-05-31 10:25:39 +05:30
gosangam 01bcc057fa feat: share sheet update 2026-05-31 10:24:32 +05:30
gosangam 8bd82a0e8a feat: image compress before upload 2026-05-31 09:56:53 +05:30
gosangam 5506afa758 feat: share post to other apps 2026-05-31 08:44:29 +05:30
gosangam 88f1b08675 feat: share post to other apps 2026-05-31 08:20:06 +05:30
gosangam 336f478f8b feat: share post to other apps 2026-05-31 08:04:46 +05:30
gosangam 03b8f85e06 feat: add reading settings sync 2026-05-31 07:07:50 +05:30
gosangam 0ff8005df2 ui fixes 2026-05-31 06:50:05 +05:30
gosangam 0ae3334d98 type fixes 2026-05-31 00:35:00 +05:30
gosangam ec20eb996b ui enhancements 2026-05-31 00:26:05 +05:30
402 changed files with 35769 additions and 12137 deletions
+20
View File
@@ -0,0 +1,20 @@
NEXT_PUBLIC_SITE_URL=https://prabhupadasays.org
# BFF API Server (server-side fetches)
BFF_URL=http://localhost:8080
# BFF API Server (browser-side fetches; must be publicly reachable in prod)
NEXT_PUBLIC_BFF_URL=http://localhost:8080
# RTF Viewer (development only)
ALLOW_RTF=true
NEXT_PUBLIC_OAUTH_ENABLED=true
# SOKETI
NEXT_PUBLIC_SOKETI_KEY=spsays
NEXT_PUBLIC_SOKETI_HOST=ws.prabhupadasays.org
NEXT_PUBLIC_SOKETI_PORT=443
NEXT_PUBLIC_SOKETI_FORCE_TLS=true
# Analytics
NEXT_PUBLIC_RYBBIT_HOST=https://analytics.krispire.cloud
+1 -1
View File
@@ -31,7 +31,7 @@ yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
.env
# vercel
.vercel
+60
View File
@@ -0,0 +1,60 @@
"use client";
/**
* Shared layout for the auth pages (login, signup, forgot-password,
* reset-password). Provides the centered card frame so each page only
* has to render its own form.
*
* Also gates the routes: a signed-in user landing on any /login, /signup,
* etc. URL is bounced to /u/me (or the `next` query param if present).
* The check happens client-side because auth state lives in
* AuthProvider — there's no server-side cookie inspection here yet, and
* the redirect is purely cosmetic (logged-in users don't gain anything
* from re-seeing these forms, and signing in over an existing session
* would just no-op).
*/
import { Suspense, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useAuth } from "@/components/auth/auth-provider";
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
// useSearchParams forces a CSR bailout during prerender, so Next.js
// requires a Suspense boundary anywhere it's read. The gate is the
// only consumer here — isolate it so the rest of the layout can still
// SSR normally.
return (
<main className="flex items-center justify-center px-4 py-12 min-h-[90vh]">
<Suspense fallback={<AuthShell>{children}</AuthShell>}>
<AuthGate>{children}</AuthGate>
</Suspense>
</main>
);
}
function AuthShell({ children }: { children: React.ReactNode }) {
return <div className="w-full max-w-md">{children}</div>;
}
function AuthGate({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
const params = useSearchParams();
useEffect(() => {
if (loading || !user) return;
const next = params.get("next");
router.replace(next && next.startsWith("/") ? next : "/u/me");
}, [loading, user, router, params]);
// Don't flash the form for an instant before the redirect fires.
if (!loading && user) {
return <p className="text-sm text-muted-foreground">Redirecting…</p>;
}
return <AuthShell>{children}</AuthShell>;
}
+63
View File
@@ -0,0 +1,63 @@
"use client";
import { Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { AuthCard, AuthFooterLink } from "@/components/auth/auth-card";
import { LoginForm } from "@/components/auth/login-form";
function LoginContent() {
const router = useRouter();
const params = useSearchParams();
const next = params.get("next") ?? "/u/me";
const oauthError = params.get("oauth_error");
return (
<AuthCard
title="Welcome back"
description="Log in to access your bookmarks, notes, and collections."
footer={
<AuthFooterLink prefix="No account?" href="/signup" label="Create one" />
}
>
{oauthError ? (
<p className="text-sm text-destructive">
{oauthErrorMessage(oauthError)}
</p>
) : null}
<LoginForm
oauthNext={next}
onSuccess={() => {
router.push(next);
router.refresh();
}}
/>
</AuthCard>
);
}
function oauthErrorMessage(code: string): string {
switch (code) {
case "invalid_state":
case "missing_state":
return "Your sign-in link expired. Please try again.";
case "exchange_failed":
case "profile_failed":
return "Sign-in failed while talking to the provider. Please try again.";
case "unknown_provider":
return "That sign-in provider isn't configured. Please use email or another method.";
case "unavailable":
return "Social sign-in is temporarily unavailable. Please use email/password.";
case "server":
return "Something went wrong on our side. Please try again in a moment.";
default:
return "Sign-in failed. Please try again.";
}
}
export default function LoginPage() {
return (
<Suspense fallback={null}>
<LoginContent />
</Suspense>
);
}
@@ -15,6 +15,7 @@ export default function SignUpPage() {
}
>
<SignUpForm
oauthNext="/u/me"
onSuccess={() => {
router.push("/u/me");
router.refresh();
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function BbdChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/bbd/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Beyond Birth and Death" })} />
<PageHeader sticky>
<Link
href="/books/bbd"
@@ -77,12 +81,15 @@ export default async function BbdChapterPage({
>
&larr; Beyond Birth and Death
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function BbdChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getBbdMeta, getBbdIntroSegments } from "@/lib/bbd";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function BbdPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bbd", name: "Beyond Birth and Death" })} />
<PageHeader>
<Link
href="/books"
@@ -1,10 +1,11 @@
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
export const dynamic = "force-dynamic";
@@ -101,9 +102,12 @@ export default async function VersePage({
>
&larr; Bhagavad-gītā
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<div className="mt-2 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -0,0 +1,86 @@
export const dynamic = "force-dynamic";
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { notFound } from "next/navigation";
import Link from "next/link";
import { getChapterVerseIndex, getProseChapter } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { Card, CardContent } from "@/components/ui/card";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
export async function generateMetadata({
params,
}: {
params: Promise<{ chapter: string }>;
}): Promise<Metadata> {
const { chapter } = await params;
// Non-numeric segments are front matter (dedication, foreword, …).
if (isNaN(parseInt(chapter))) {
const segment = await getProseChapter("bg", 0, chapter);
return buildOgMeta({
title: BOOK_NAMES["bg"],
detail: segment?.title ?? "Front Matter",
});
}
return buildOgMeta({ title: BOOK_NAMES["bg"], detail: `Chapter ${chapter}` });
}
export default async function ChapterPage({
params,
}: {
params: Promise<{ chapter: string }>;
}) {
const { chapter } = await params;
// Front matter — non-numeric slugs (dedication, foreword, preface,
// introduction, disciplic-succession) resolve to prose intro-segments.
if (isNaN(parseInt(chapter))) {
const segment = await getProseChapter("bg", 0, chapter);
if (!segment) notFound();
return (
<div className="min-h-screen bg-background">
<PageHeader sticky>
<Link
href="/books/bg"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; {BOOK_NAMES["bg"]}
</Link>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{segment.paragraphs.map((paragraph, i) => (
<div
key={i}
className="text-base text-foreground leading-relaxed"
dangerouslySetInnerHTML={{ __html: paragraph }}
/>
))}
</div>
</CardContent>
</Card>
</main>
</div>
);
}
// Numeric chapters — redirect to the first verse.
const verseIndex = await getChapterVerseIndex("bg", null, parseInt(chapter));
if (verseIndex.length === 0) {
notFound();
}
redirect(`/books/bg/${chapter}/${verseIndex[0].verse}`);
}
+115
View File
@@ -0,0 +1,115 @@
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { getBookMeta, getProseChapters } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count";
export function generateMetadata(): Metadata {
return ogForBook("bg");
}
type ChapterInfo = {
number: number;
title: string;
totalVerses: number;
};
export default async function BgPage() {
const [meta, introSegments] = await Promise.all([
getBookMeta("bg"),
getProseChapters("bg", 0, "intro-segment"),
]);
if (!meta) notFound();
const intro = meta as unknown as {
book: string;
title: string;
author: string;
totalChapters: number;
chapters: ChapterInfo[];
};
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bg", name: "Bhagavad-gītā As It Is" })} />
<PageHeader>
<Link
href="/books"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; Books
</Link>
<h1 className="mt-3 text-3xl font-bold text-foreground">
{intro.title}
</h1>
<p className="mt-1 text-muted-foreground">{intro.author}</p>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
{/* Front Matter */}
{introSegments.length > 0 && (
<div>
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
Front Matter
</h2>
<Card className="divide-y divide-border py-0">
<CardContent className="p-0">
<ul className="divide-y divide-border">
{introSegments.map((seg) => (
<li key={seg.slug}>
<Link
href={`/books/bg/${seg.slug}`}
className="flex px-5 py-4 hover:bg-muted transition-colors"
>
<p className="font-medium text-foreground">
{seg.title}
</p>
</Link>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
)}
{/* Chapters */}
<div>
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
Chapters
</h2>
<Card className="divide-y divide-border py-0">
<CardContent className="p-0">
<ul className="divide-y divide-border">
{intro.chapters.map((ch) => (
<li key={ch.number}>
<Link
href={`/books/bg/${ch.number}`}
className="flex flex-col sm:flex-row sm:items-center sm:justify-between px-5 py-4 gap-1 sm:gap-0 hover:bg-muted transition-colors"
>
<div>
<span className="text-sm text-primary font-medium">
Chapter {ch.number}
</span>
<p className="font-medium text-foreground">
{ch.title}
</p>
</div>
<span className="text-sm text-muted-foreground sm:shrink-0 sm:ml-4">
<ExtractedVerseCount count={ch.totalVerses} />
</span>
</Link>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
</main>
</div>
);
}
@@ -10,12 +10,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
export const dynamic = "force-dynamic";
@@ -62,6 +65,7 @@ export default async function BkChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/bk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Bhagavāner Kathā" })} />
<PageHeader sticky>
<Link
href="/books/bk"
@@ -69,12 +73,15 @@ export default async function BkChapterPage({
>
&larr; Bhagavāner Kathā
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -92,7 +99,7 @@ export default async function BkChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getBkMeta } from "@/lib/bk";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function BkPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bk", name: "Bhagavāner Kathā" })} />
<PageHeader>
<Link
href="/books"
@@ -72,11 +72,16 @@ export function BooksGrid({ books }: { books: BookInfo[] }) {
);
const filtered = useMemo(() => {
if (!query.trim()) return books;
const q = normalize(query.trim());
return normalizedBooks
.filter((entry) => entry.searchText.includes(q))
.map((entry) => entry.book);
const base = query.trim()
? normalizedBooks
.filter((entry) =>
entry.searchText.includes(normalize(query.trim())),
)
.map((entry) => entry.book)
: books;
return [...base].sort((a, b) =>
normalize(a.title).localeCompare(normalize(b.title)),
);
}, [books, normalizedBooks, query]);
return (
@@ -2,7 +2,8 @@ import type { Metadata } from "next";
import fs from "fs";
import path from "path";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import { notFound } from "next/navigation";
import {
getBookMeta,
@@ -11,8 +12,8 @@ import {
type VerseIndexEntry,
type VerseRecord,
} from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
export const dynamic = "force-dynamic";
@@ -187,9 +188,12 @@ export default async function VersePage({
>
&larr; Śrī Brahma-saṁhitā
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<div className="mt-2 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { Card, CardContent } from "@/components/ui/card";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import Link from "next/link";
import fs from "fs";
import path from "path";
@@ -59,13 +60,16 @@ export default async function BsChapterPage({
>
&larr; Śrī Brahma-saṁhitā
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{segment.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{segment.paragraphs.map((paragraph: string, i: number) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count";
import fs from "fs";
@@ -80,6 +82,7 @@ export default async function BsPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bs", name: "Śrī Brahma-saṁhitā" })} />
<PageHeader>
<Link
href="/books"
@@ -10,12 +10,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
export const dynamic = "force-dynamic";
@@ -62,6 +65,7 @@ export default async function CbhcChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/cbhc/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Śrī Caitanya-bhāgavata Commentary" })} />
<PageHeader sticky>
<Link
href="/books/cbhc"
@@ -69,12 +73,15 @@ export default async function CbhcChapterPage({
>
&larr; Śrī Caitanya-bhāgavata Commentary
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -92,7 +99,7 @@ export default async function CbhcChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getCbhcMeta } from "@/lib/cbhc";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function CbhcPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/cbhc", name: "Śrī Caitanya-bhāgavata Commentary" })} />
<PageHeader>
<Link
href="/books"
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
type ChapterInfo = {
@@ -111,9 +112,12 @@ export default async function CcVersePage({
>
&larr; {lilaInfo.name}
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {chapterInfo.title}
</h1>
<div className="mt-2 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapter}: {chapterInfo.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -0,0 +1,227 @@
export const dynamic = "force-dynamic";
import type { Metadata } from "next";
import Link from "next/link";
import { redirect, notFound } from "next/navigation";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { getBookMeta, getProseChapter, getChapterVerseIndex } from "@/lib/api";
import { CC_LILAS } from "@/lib/constants";
import { buildOgMeta } from "@/lib/og";
import { PageHeader } from "@/components/layout/page-header";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { FindInPage } from "@/components/layout/find-in-page";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { cn } from "@/lib/utils";
type ChapterInfo = { number: number; title: string; totalVerses: number };
type LilaInfo = {
key: string;
name: string;
totalChapters: number;
totalVerses: number;
chapters: ChapterInfo[];
};
type VerseEntry = { verse: number; verseEnd?: number | null; reference: string };
export async function generateMetadata({
params,
}: {
params: Promise<{ lila: string; chapter: string }>;
}): Promise<Metadata> {
const { lila, chapter } = await params;
const lilaName = CC_LILAS[lila] || lila;
return buildOgMeta({
title: "Śrī Caitanya-caritāmṛta",
detail: `${lilaName}, Chapter ${chapter}`,
});
}
function verseLabel(v: VerseEntry): string {
return v.verseEnd ? `${v.verse}–${v.verseEnd}` : String(v.verse);
}
// ---------------------------------------------------------------------------
// Top/bottom navigation — mirrors the verse pages' VerseNav: a Contents
// toggle (mobile) listing this chapter's verses, Previous/Next (chapter), and
// a "Chapter X of N" label.
// ---------------------------------------------------------------------------
function ChapterNav({
lila,
chapters,
current,
chapterTitle,
verseIndex,
}: {
lila: string;
chapters: ChapterInfo[];
current: number;
chapterTitle: string;
verseIndex: VerseEntry[];
}) {
const idx = chapters.findIndex((c) => c.number === current);
const prev = idx > 0 ? chapters[idx - 1] : null;
const next = idx < chapters.length - 1 ? chapters[idx + 1] : null;
// The contents drawer lists the active chapter's verses (same as the
// verse-page sidebar) so the reader can jump straight in.
const groups: MobileNavGroup[] = [
{
title: "Verses",
items: verseIndex.map((v) => ({
slug: String(v.verse),
label: `Verse ${verseLabel(v)}`,
href: `/books/cc/${lila}/${current}/${v.verse}`,
type: "verse",
})),
},
];
return (
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<MobileNav
groups={groups}
currentSlug=""
bookTitle={`Chapter ${current}: ${chapterTitle}`}
/>
{prev ? (
<Link
href={`/books/cc/${lila}/${prev.number}`}
className={cn(buttonVariants({ variant: "outline", size: "sm" }), "gap-1.5")}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
<span className="text-sm text-muted-foreground">
Chapter {current} of {chapters.length}
</span>
{next ? (
<Link
href={`/books/cc/${lila}/${next.number}`}
className={cn(buttonVariants({ variant: "outline", size: "sm" }), "gap-1.5")}
>
<span className="hidden sm:inline">Next</span>
<ChevronRight className="size-4" />
</Link>
) : (
<div />
)}
</div>
);
}
export default async function CcChapterPage({
params,
}: {
params: Promise<{ lila: string; chapter: string }>;
}) {
const { lila, chapter } = await params;
const chapterNum = parseInt(chapter);
const [meta, summary, verseIndex] = await Promise.all([
getBookMeta("cc"),
getProseChapter("cc", 0, `${lila}-${chapterNum}`),
getChapterVerseIndex("cc", lila, chapterNum),
]);
if (!meta) notFound();
const lilaInfo = (meta.lilas as LilaInfo[] | undefined)?.find(
(l) => l.key === lila,
);
if (!lilaInfo) notFound();
const chapterInfo = lilaInfo.chapters.find((ch) => ch.number === chapterNum);
if (!chapterInfo) notFound();
if (verseIndex.length === 0) notFound();
const firstVerse = verseIndex[0].verse;
// Chapters without a summary (e.g. Ādi 7) go straight to the verses.
if (!summary || summary.paragraphs.length === 0) {
redirect(`/books/cc/${lila}/${chapterNum}/${firstVerse}`);
}
return (
<div className="min-h-screen bg-background">
<PageHeader sticky>
<Link
href={`/books/cc/${lila}`}
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; {lilaInfo.name}
</Link>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapterNum}: {chapterInfo.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
{/* Desktop sidebar — verses of the active chapter (matches the
verse-page sidebar) so the reader can jump to any verse. */}
<aside className="hidden lg:block w-64 shrink-0 sticky top-[89px] self-start max-h-[calc(100vh-165px)] overflow-y-auto py-8 pr-4">
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3 px-2">
Verses
</h2>
<nav className="grid grid-cols-3 gap-1">
{verseIndex.map((v) => (
<Link
key={v.verse}
href={`/books/cc/${lila}/${chapterNum}/${v.verse}`}
className="px-2 py-1 rounded-md text-sm tabular-nums text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
>
{verseLabel(v)}
</Link>
))}
</nav>
</aside>
{/* Main — chapter summary */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-6">
{/* Top navigation */}
<ChapterNav
lila={lila}
chapters={lilaInfo.chapters}
current={chapterNum}
chapterTitle={chapterInfo.title}
verseIndex={verseIndex}
/>
<p className="text-sm font-bold uppercase tracking-wider text-muted-foreground">
Chapter Summary
</p>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{summary.paragraphs.map((paragraph, i) => (
<div
key={i}
className="text-base text-foreground leading-relaxed"
dangerouslySetInnerHTML={{ __html: paragraph }}
/>
))}
</div>
</CardContent>
</Card>
{/* Bottom navigation */}
<ChapterNav
lila={lila}
chapters={lilaInfo.chapters}
current={chapterNum}
chapterTitle={chapterInfo.title}
verseIndex={verseIndex}
/>
</main>
</div>
</div>
);
}
@@ -1,10 +1,11 @@
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { notFound } from "next/navigation";
import { Card, CardContent } from "@/components/ui/card";
import { FindInPage } from "@/components/layout/find-in-page";
import { ExtractedVerseCount } from "../../extracted-verse-count";
import { getBookMeta } from "@/lib/api";
import { getBookMeta, getProseChapter } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES, CC_LILAS } from "@/lib/constants";
@@ -14,12 +15,15 @@ export async function generateMetadata({
params: Promise<{ lila: string }>;
}): Promise<Metadata> {
const { lila } = await params;
const lilaName = CC_LILAS[lila] || lila;
const bookTitle = BOOK_NAMES["cc"] || "Śrī Caitanya-caritāmṛta";
return buildOgMeta({
title: bookTitle,
detail: lilaName,
});
// A non-līlā segment (front-flap, preface, introduction) is front matter.
if (!CC_LILAS[lila]) {
const segment = await getProseChapter("cc", 0, lila);
if (segment) {
return buildOgMeta({ title: bookTitle, detail: segment.title });
}
}
return buildOgMeta({ title: bookTitle, detail: CC_LILAS[lila] || lila });
}
type ChapterInfo = {
@@ -47,7 +51,48 @@ export default async function LilaPage({
const lilas = meta.lilas as LilaInfo[];
const lilaInfo = lilas.find((l) => l.key === lila);
if (!lilaInfo) notFound();
// Front matter — the param is not a līlā key, so resolve it as a prose
// intro-segment (front-flap, preface, introduction).
if (!lilaInfo) {
const segment = await getProseChapter("cc", 0, lila);
if (!segment) notFound();
return (
<div className="min-h-screen bg-background">
<PageHeader sticky>
<Link
href="/books/cc"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; Śrī Caitanya-caritāmṛta
</Link>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{segment.paragraphs.map((paragraph, i) => (
<div
key={i}
className="text-base text-foreground leading-relaxed"
dangerouslySetInnerHTML={{ __html: paragraph }}
/>
))}
</div>
</CardContent>
</Card>
</main>
</div>
);
}
const totalVerses = lilaInfo.chapters.reduce(
(sum, ch) => sum + ch.totalVerses,
@@ -1,8 +1,10 @@
import type { Metadata } from "next";
import Link from "next/link";
import { getBookMeta } from "@/lib/api";
import { getBookMeta, getProseChapters } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { ExtractedVerseCount } from "../extracted-verse-count";
@@ -26,7 +28,10 @@ export function generateMetadata(): Metadata {
}
export default async function CcPage() {
const meta = await getBookMeta("cc");
const [meta, introSegments] = await Promise.all([
getBookMeta("cc"),
getProseChapters("cc", 0, "intro-segment"),
]);
if (!meta) {
return (
<div className="p-8 text-center text-muted-foreground">
@@ -41,6 +46,7 @@ export default async function CcPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/cc", name: "Śrī Caitanya-caritāmṛta" })} />
<PageHeader>
<Link
href="/books"
@@ -51,7 +57,33 @@ export default async function CcPage() {
<h1 className="mt-3 text-3xl font-bold text-foreground">{title}</h1>
<p className="mt-1 text-muted-foreground">{author}</p>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
{introSegments.length > 0 && (
<div>
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
Front Matter
</h2>
<Card className="divide-y divide-border py-0">
<CardContent className="p-0">
<ul className="divide-y divide-border">
{introSegments.map((seg) => (
<li key={seg.slug}>
<Link
href={`/books/cc/${seg.slug}`}
className="flex px-5 py-4 hover:bg-muted transition-colors"
>
<p className="font-medium text-foreground">
{seg.title}
</p>
</Link>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
)}
<Card className="divide-y divide-border">
<CardContent className="p-0">
<ul className="divide-y divide-border">
@@ -6,7 +6,10 @@ import { Card, CardHeader, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { BOOK_NAMES } from "@/lib/constants";
// ---------------------------------------------------------------------------
// Types
@@ -216,9 +219,17 @@ function VerseCard({
Purport
</h3>
<div className="text-base text-foreground leading-relaxed space-y-3">
{verse.purport.map((p, i) => (
<p key={i} dangerouslySetInnerHTML={{ __html: p }} />
))}
{verse.purport.map((p, i) =>
// Cited ślokas come wrapped in <blockquote class="shloka"> — a
// block element that can't live inside a <p>, so render those
// in a <div> (styled centered + accent via globals.css); plain
// prose paragraphs stay as <p>.
/^\s*<blockquote/i.test(p) ? (
<div key={i} dangerouslySetInnerHTML={{ __html: p }} />
) : (
<p key={i} dangerouslySetInnerHTML={{ __html: p }} />
),
)}
</div>
</div>
)}
@@ -389,6 +400,19 @@ export function ChapterViewer({
return (
<>
{/* Structured data for the currently-shown verse (SSR'd for the initial
verse, updates on client navigation). */}
{currentVerse ? (
<JsonLd
data={articleJsonLd({
path: `${basePath}/${currentVerseNum}`,
headline: `${BOOK_NAMES[book] ?? book} ${currentVerse.reference}`,
description: currentVerse.translation,
isPartOf: BOOK_NAMES[book] ?? book,
})}
/>
) : null}
{/* ---- Sidebar: Verse Index ---- */}
{/* Sticky sidebar. The max-h leaves 76px at the viewport bottom for
the site footer so the sidebar doesn't slide behind it when the
@@ -440,7 +464,8 @@ export function ChapterViewer({
mobileNavBookTitle={mobileNavBookTitle}
/>
{/* Verse content */}
{/* Verse content — id targeted by each page header's FindInPage. */}
<div id="book-content">
{loading && !currentVerse ? (
<div className="flex items-center justify-center py-24 text-muted-foreground gap-2">
<Loader2 className="size-5 animate-spin" />
@@ -453,6 +478,7 @@ export function ChapterViewer({
Verse not found.
</div>
)}
</div>
{/* Bottom navigation */}
<VerseNav
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -76,6 +79,7 @@ export default async function DsChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/ds/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Dialectic Spiritualism" })} />
<PageHeader sticky>
<Link
href="/books/ds"
@@ -83,12 +87,15 @@ export default async function DsChapterPage({
>
&larr; Dialectic Spiritualism
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{parentChapterNum != null && (
<span className="text-primary">Chapter {parentChapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{parentChapterNum != null && (
<span className="text-primary">Chapter {parentChapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -106,7 +113,7 @@ export default async function DsChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getDsMeta, getDsIntroSegments } from "@/lib/ds";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function DsPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ds", name: "Dialectic Spiritualism" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function EjChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/ej/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Easy Journey to Other Planets" })} />
<PageHeader sticky>
<Link
href="/books/ej"
@@ -77,12 +81,15 @@ export default async function EjChapterPage({
>
&larr; Easy Journey to Other Planets
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function EjChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getEjMeta, getEjIntroSegments } from "@/lib/ej";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function EjPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ej", name: "Easy Journey to Other Planets" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function EkChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/ek/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Elevation to Kṛṣṇa Consciousness" })} />
<PageHeader sticky>
<Link
href="/books/ek"
@@ -77,12 +81,15 @@ export default async function EkChapterPage({
>
&larr; Elevation to Kṛṣṇa Consciousness
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function EkChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getEkMeta, getEkIntroSegments } from "@/lib/ek";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function EkPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ek", name: "Elevation to Kṛṣṇa Consciousness" })} />
<PageHeader>
<Link
href="/books"
@@ -10,12 +10,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
export const dynamic = "force-dynamic";
@@ -66,6 +69,7 @@ export default async function GgChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/gg/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Gītār Gān" })} />
<PageHeader sticky>
<Link
href="/books/gg"
@@ -73,12 +77,15 @@ export default async function GgChapterPage({
>
&larr; Gītār Gān
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -96,7 +103,7 @@ export default async function GgChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getGgMeta, getGgIntroSegments } from "@/lib/gg";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function GgPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/gg", name: "Gītār Gān" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function HkhChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/hkh/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Kṛṣṇa Consciousness Handbook" })} />
<PageHeader sticky>
<Link
href="/books/hkh"
@@ -77,12 +81,15 @@ export default async function HkhChapterPage({
>
&larr; The Kṛṣṇa Consciousness Handbook
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function HkhChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getHkhMeta, getHkhIntroSegments } from "@/lib/hkh";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function HkhPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/hkh", name: "The Kṛṣṇa Consciousness Handbook" })} />
<PageHeader>
<Link
href="/books"
@@ -11,12 +11,15 @@ import {
type IsoSidebarItem,
} from "@/lib/iso";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Type guards
@@ -102,6 +105,7 @@ export default async function IsoMantraPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/iso/${slug}`, headline: content.title ?? "Śrī Īśopaniṣad", isPartOf: "Śrī Īśopaniṣad" })} />
<PageHeader sticky>
<Link
href="/books/iso"
@@ -109,15 +113,18 @@ export default async function IsoMantraPage({
>
&larr; Śrī Īśopaniṣad
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{verse && (
<>
<span className="text-primary">{verse.reference}: </span>
{verse.bookTitle}
</>
)}
{intro && intro.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{verse && (
<>
<span className="text-primary">{verse.reference}: </span>
{verse.bookTitle}
</>
)}
{intro && intro.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -157,7 +164,7 @@ export default async function IsoMantraPage({
function VerseContent({ verse }: { verse: IsoVerse }) {
return (
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8 space-y-8">
{/* Devanagari */}
<section>
@@ -233,7 +240,7 @@ function VerseContent({ verse }: { verse: IsoVerse }) {
function IntroContent({ intro }: { intro: IsoIntroSegment }) {
return (
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{intro.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getIsoMeta, getIsoIntroSegments } from "@/lib/iso";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -28,6 +30,7 @@ export default async function IsoPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/iso", name: "Śrī Īśopaniṣad" })} />
<PageHeader>
<Link
href="/books"
@@ -6,12 +6,15 @@ import { notFound } from "next/navigation";
import { getProseChapter, getProseChapters } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
export async function generateMetadata({
params,
@@ -65,6 +68,7 @@ export default async function KbChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/kb/${vol}/${slug}`, headline: chapter.title, description: chapter.paragraphs?.[0], isPartOf: "Kṛṣṇa, The Supreme Personality of Godhead" })} />
<PageHeader sticky>
<Link
href={`/books/kb/${vol}`}
@@ -72,12 +76,15 @@ export default async function KbChapterPage({
>
&larr; Volume {vol}
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterLabel && (
<span className="text-primary">{chapterLabel}: </span>
)}
{chapter.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterLabel && (
<span className="text-primary">{chapterLabel}: </span>
)}
{chapter.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function KbChapterPage({
/>
{/* Chapter content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{chapter.paragraphs.map((paragraph, i) => (
@@ -4,7 +4,7 @@ import { notFound } from "next/navigation";
import { getBookMeta, getProseChapters } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { Card, CardContent } from "@/components/ui/card";
export async function generateMetadata({
@@ -3,7 +3,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
@@ -27,6 +29,7 @@ export default async function KbPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/kb", name: "Kṛṣṇa, The Supreme Personality of Godhead" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function LclChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/lcl/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Life Comes from Life" })} />
<PageHeader sticky>
<Link
href="/books/lcl"
@@ -77,12 +81,15 @@ export default async function LclChapterPage({
>
&larr; Life Comes from Life
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function LclChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getLclMeta, getLclIntroSegments } from "@/lib/lcl";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function LclPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/lcl", name: "Life Comes from Life" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
export async function generateMetadata({
params,
@@ -66,6 +69,7 @@ export default async function LobChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/lob/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Light of the Bhāgavata" })} />
<PageHeader sticky>
<Link
href="/books/lob"
@@ -73,12 +77,15 @@ export default async function LobChapterPage({
>
&larr; Light of the Bh&#257;gavata
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Text {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Text {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -96,7 +103,7 @@ export default async function LobChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) =>
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getLobMeta, getLobIntroSegments } from "@/lib/lob";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function LobPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/lob", name: "Light of the Bhāgavata" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
export async function generateMetadata({
params,
@@ -66,6 +69,7 @@ export default async function MgChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/mg/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Kṛṣṇa Consciousness: The Matchless Gift" })} />
<PageHeader sticky>
<Link
href="/books/mg"
@@ -73,12 +77,15 @@ export default async function MgChapterPage({
>
&larr; Kṛṣṇa Consciousness: The Matchless Gift
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -96,7 +103,7 @@ export default async function MgChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getMgMeta, getMgIntroSegments } from "@/lib/mg";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function MgPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/mg", name: "Kṛṣṇa Consciousness: The Matchless Gift" })} />
<PageHeader>
<Link
href="/books"
@@ -1,7 +1,8 @@
import fs from "fs";
import path from "path";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import { notFound } from "next/navigation";
import {
getBookMeta,
@@ -10,8 +11,8 @@ import {
type VerseIndexEntry,
type VerseRecord,
} from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
import type { Metadata } from "next";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
@@ -187,9 +188,12 @@ export default async function VersePage({
>
&larr; Mukunda-mālā-stotra
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<div className="mt-2 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { Card, CardContent } from "@/components/ui/card";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import Link from "next/link";
import fs from "fs";
import path from "path";
@@ -59,13 +60,16 @@ export default async function MmChapterPage({
>
&larr; Mukunda-mālā-stotra
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{segment.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{segment.paragraphs.map((paragraph: string, i: number) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count";
import fs from "fs";
@@ -79,6 +81,7 @@ export default async function MmPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/mm", name: "Mukunda-mālā-stotra" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function MogChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/mog/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Message of Godhead" })} />
<PageHeader sticky>
<Link
href="/books/mog"
@@ -77,12 +81,15 @@ export default async function MogChapterPage({
>
&larr; Message of Godhead
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function MogChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getMogMeta, getMogIntroSegments } from "@/lib/mog";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function MogPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/mog", name: "Message of Godhead" })} />
<PageHeader>
<Link
href="/books"
@@ -4,7 +4,8 @@ import type { Metadata } from "next";
import fs from "fs";
import path from "path";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import { notFound } from "next/navigation";
import {
getBookMeta,
@@ -14,8 +15,8 @@ import {
type VerseRecord,
} from "@/lib/api";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
type NbsMeta = {
book: string;
@@ -192,9 +193,12 @@ export default async function VersePage({
>
&larr; Nārada-bhakti-sūtra
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<div className="mt-2 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { Card, CardContent } from "@/components/ui/card";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import Link from "next/link";
import fs from "fs";
import path from "path";
@@ -62,13 +63,16 @@ export default async function NbsChapterPage({
>
&larr; Nārada-bhakti-sūtra
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{segment.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{segment.paragraphs.map((paragraph: string, i: number) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count";
import fs from "fs";
@@ -79,6 +81,7 @@ export default async function NbsPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/nbs", name: "Nārada-bhakti-sūtra" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function NodChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/nod/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Nectar of Devotion" })} />
<PageHeader sticky>
<Link
href="/books/nod"
@@ -77,12 +81,15 @@ export default async function NodChapterPage({
>
&larr; The Nectar of Devotion
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function NodChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getNodMeta, getNodIntroSegments } from "@/lib/nod";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -27,6 +29,7 @@ export default async function NodPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/nod", name: "The Nectar of Devotion" })} />
<PageHeader>
<Link
href="/books"
@@ -11,12 +11,15 @@ import {
type NoiSidebarItem,
} from "@/lib/noi";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Type guards
@@ -93,6 +96,7 @@ export default async function NoiTextPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/noi/${slug}`, headline: content.title ?? "The Nectar of Instruction", isPartOf: "The Nectar of Instruction" })} />
<PageHeader sticky>
<Link
href="/books/noi"
@@ -100,15 +104,18 @@ export default async function NoiTextPage({
>
&larr; The Nectar of Instruction
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{verse && (
<>
<span className="text-primary">Text {verse.text}: </span>
{verse.title}
</>
)}
{intro && intro.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{verse && (
<>
<span className="text-primary">Text {verse.text}: </span>
{verse.title}
</>
)}
{intro && intro.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -148,7 +155,7 @@ export default async function NoiTextPage({
function VerseContent({ verse }: { verse: NoiVerse }) {
return (
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8 space-y-8">
{/* Devanagari */}
<section>
@@ -224,7 +231,7 @@ function VerseContent({ verse }: { verse: NoiVerse }) {
function IntroContent({ intro }: { intro: NoiIntroSegment }) {
return (
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{intro.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getNoiMeta, getNoiIntroSegments } from "@/lib/noi";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function NoiPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/noi", name: "The Nectar of Instruction" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function OchkmChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/ochkm/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "On Chanting Hare Kṛṣṇa" })} />
<PageHeader sticky>
<Link
href="/books/ochkm"
@@ -77,12 +81,15 @@ export default async function OchkmChapterPage({
>
&larr; On Chanting the Hare Kṛṣṇa Mantra
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function OchkmChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getOchkmMeta, getOchkmIntroSegments } from "@/lib/ochkm";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function OchkmPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ochkm", name: "On Chanting Hare Kṛṣṇa" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function OwkChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/owk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "On the Way to Kṛṣṇa" })} />
<PageHeader sticky>
<Link
href="/books/owk"
@@ -77,12 +81,15 @@ export default async function OwkChapterPage({
>
&larr; On the Way to Kṛṣṇa
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function OwkChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getOwkMeta, getOwkIntroSegments } from "@/lib/owk";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function OwkPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/owk", name: "On the Way to Kṛṣṇa" })} />
<PageHeader>
<Link
href="/books"
@@ -1,6 +1,6 @@
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { getAllBooks, type BookMeta } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BooksGrid, type BookInfo } from "./books-grid";
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -65,6 +68,7 @@ export default async function PkChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/pk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Prabhupāda-Kṛpā" })} />
<PageHeader sticky>
<Link
href="/books/pk"
@@ -72,12 +76,15 @@ export default async function PkChapterPage({
>
&larr; Prayers of King Kulaśekhara
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Verse {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Verse {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -95,7 +102,7 @@ export default async function PkChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getPkMeta } from "@/lib/pk";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function PkPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/pk", name: "Prabhupāda-Kṛpā" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function PoyChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/poy/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Perfection of Yoga" })} />
<PageHeader sticky>
<Link
href="/books/poy"
@@ -77,12 +81,15 @@ export default async function PoyChapterPage({
>
&larr; The Perfection of Yoga
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function PoyChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getPoyMeta, getPoyIntroSegments } from "@/lib/poy";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function PoyPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/poy", name: "The Perfection of Yoga" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function PqpaChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/pqpa/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Perfect Questions, Perfect Answers" })} />
<PageHeader sticky>
<Link
href="/books/pqpa"
@@ -77,12 +81,15 @@ export default async function PqpaChapterPage({
>
&larr; Perfect Questions Perfect Answers
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function PqpaChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getPqpaMeta, getPqpaIntroSegments } from "@/lib/pqpa";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function PqpaPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/pqpa", name: "Perfect Questions, Perfect Answers" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function RvChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/rv/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Rāja-vidyā: The King of Knowledge" })} />
<PageHeader sticky>
<Link
href="/books/rv"
@@ -77,12 +81,15 @@ export default async function RvChapterPage({
>
&larr; Rāja-vidyā: The King of Knowledge
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function RvChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getRvMeta, getRvIntroSegments } from "@/lib/rv";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function RvPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/rv", name: "Rāja-vidyā: The King of Knowledge" })} />
<PageHeader>
<Link
href="/books"
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
type ChapterInfo = {
@@ -107,9 +108,12 @@ export default async function SbVersePage({
>
&larr; Canto {cantoInfo.number}: {cantoInfo.title}
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {chapterInfo.title}
</h1>
<div className="mt-2 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapter}: {chapterInfo.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -1,10 +1,11 @@
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { notFound } from "next/navigation";
import { Card, CardContent } from "@/components/ui/card";
import { FindInPage } from "@/components/layout/find-in-page";
import { ExtractedVerseCount } from "../../extracted-verse-count";
import { getBookMeta } from "@/lib/api";
import { getBookMeta, getProseChapter } from "@/lib/api";
import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
@@ -15,6 +16,11 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { canto } = await params;
const title = BOOK_NAMES["sb"] || "Śrīmad-Bhāgavatam";
// A non-numeric segment (preface, introduction) is front matter.
if (isNaN(parseInt(canto))) {
const segment = await getProseChapter("sb", 0, canto);
if (segment) return buildOgMeta({ title, detail: segment.title });
}
return buildOgMeta({ title, detail: `Canto ${canto}` });
}
@@ -38,6 +44,49 @@ export default async function CantoPage({
}) {
const { canto } = await params;
const cantoNum = parseInt(canto);
// Front matter — a non-numeric param (preface, introduction) resolves to a
// prose intro-segment.
if (isNaN(cantoNum)) {
const segment = await getProseChapter("sb", 0, canto);
if (!segment) notFound();
return (
<div className="min-h-screen bg-background">
<PageHeader sticky>
<Link
href="/books/sb"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; Śrīmad-Bhāgavatam
</Link>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{segment.paragraphs.map((paragraph, i) => (
<div
key={i}
className="text-base text-foreground leading-relaxed"
dangerouslySetInnerHTML={{ __html: paragraph }}
/>
))}
</div>
</CardContent>
</Card>
</main>
</div>
);
}
const meta = await getBookMeta("sb");
if (!meta) notFound();
@@ -1,10 +1,12 @@
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { ExtractedVerseCount } from "../extracted-verse-count";
import { getBookMeta } from "@/lib/api";
import { getBookMeta, getProseChapters } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { notFound } from "next/navigation";
@@ -26,7 +28,10 @@ type CantoInfo = {
};
export default async function SbPage() {
const meta = await getBookMeta("sb");
const [meta, introSegments] = await Promise.all([
getBookMeta("sb"),
getProseChapters("sb", 0, "intro-segment"),
]);
if (!meta) notFound();
const intro = meta as {
@@ -39,6 +44,7 @@ export default async function SbPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/sb", name: "Śrīmad-Bhāgavatam" })} />
<PageHeader>
<Link
href="/books"
@@ -51,7 +57,33 @@ export default async function SbPage() {
</h1>
<p className="mt-1 text-muted-foreground">{intro.author}</p>
</PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8">
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
{introSegments.length > 0 && (
<div>
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
Front Matter
</h2>
<Card className="divide-y divide-border py-0">
<CardContent className="p-0">
<ul className="divide-y divide-border">
{introSegments.map((seg) => (
<li key={seg.slug}>
<Link
href={`/books/sb/${seg.slug}`}
className="flex px-5 py-4 hover:bg-muted transition-colors"
>
<p className="font-medium text-foreground">
{seg.title}
</p>
</Link>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
)}
<Card className="divide-y divide-border">
<CardContent className="p-0">
<ul className="divide-y divide-border">
@@ -1,10 +1,11 @@
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { FindInPage } from "@/components/layout/find-in-page";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
export const dynamic = "force-dynamic";
@@ -104,9 +105,12 @@ export default async function VersePage({
>
&larr; Śrīmad-Bhāgavatam (1962)
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<div className="mt-2 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -3,7 +3,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count";
@@ -32,6 +34,7 @@ export default async function Sb1962Page() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/sb1962", name: "Śrīmad-Bhāgavatam (1962 Edition)" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -76,6 +79,7 @@ export default async function SsrChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/ssr/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Science of Self-Realization" })} />
<PageHeader sticky>
<Link
href="/books/ssr"
@@ -83,12 +87,15 @@ export default async function SsrChapterPage({
>
&larr; The Science of Self-Realization
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{parentChapterNum != null && (
<span className="text-primary">Chapter {parentChapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{parentChapterNum != null && (
<span className="text-primary">Chapter {parentChapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -106,7 +113,7 @@ export default async function SsrChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getSsrMeta, getSsrIntroSegments } from "@/lib/ssr";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function SsrPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ssr", name: "The Science of Self-Realization" })} />
<PageHeader>
<Link
href="/books"
@@ -13,12 +13,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -77,6 +80,7 @@ export default async function SvaChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/sva/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Songs of the Vaiṣṇava Ācāryas" })} />
<PageHeader sticky>
<Link
href="/books/sva"
@@ -84,12 +88,15 @@ export default async function SvaChapterPage({
>
&larr; Songs of the Vaiṣṇava Ācāryas
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{parentChapterNum != null && (
<span className="text-primary">Part {parentChapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{parentChapterNum != null && (
<span className="text-primary">Part {parentChapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -107,7 +114,7 @@ export default async function SvaChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getSvaMeta, getSvaIntroSegments } from "@/lib/sva";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function SvaPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/sva", name: "Songs of the Vaiṣṇava Ācāryas" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function TlcChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/tlc/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Caitanya" })} />
<PageHeader sticky>
<Link
href="/books/tlc"
@@ -77,12 +81,15 @@ export default async function TlcChapterPage({
>
&larr; Teachings of Lord Caitanya
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TlcChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getTlcMeta, getTlcIntroSegments } from "@/lib/tlc";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -25,6 +27,7 @@ export default async function TlcPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/tlc", name: "Teachings of Lord Caitanya" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function TlkChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/tlk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Kapila" })} />
<PageHeader sticky>
<Link
href="/books/tlk"
@@ -77,12 +81,15 @@ export default async function TlkChapterPage({
>
&larr; Teachings of Lord Kapila
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TlkChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getTlkMeta, getTlkIntroSegments } from "@/lib/tlk";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function TlkPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/tlk", name: "Teachings of Lord Kapila" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function TqkChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/tqk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Queen Kuntī" })} />
<PageHeader sticky>
<Link
href="/books/tqk"
@@ -77,12 +81,15 @@ export default async function TqkChapterPage({
>
&larr; Teachings of Queen Kuntī
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TqkChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getTqkMeta, getTqkIntroSegments } from "@/lib/tqk";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function TqkPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/tqk", name: "Teachings of Queen Kuntī" })} />
<PageHeader>
<Link
href="/books"
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -70,6 +73,7 @@ export default async function TtpChapterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/ttp/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Kapila" })} />
<PageHeader sticky>
<Link
href="/books/ttp"
@@ -77,12 +81,15 @@ export default async function TtpChapterPage({
>
&larr; Transcendental Teachings of Prahlāda Mahārāja
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Chapter {chapterNum}: </span>
)}
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TtpChapterPage({
/>
{/* Content */}
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getTtpMeta } from "@/lib/ttp";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function TtpPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ttp", name: "Teachings of Lord Kapila" })} />
<PageHeader>
<Link
href="/books"
@@ -6,18 +6,20 @@ import { notFound } from "next/navigation";
import {
getVaContent,
getVaSidebarItems,
type VaChapter,
type VaSidebarItem,
} from "@/lib/va";
import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { FindInPage } from "@/components/layout/find-in-page";
// ---------------------------------------------------------------------------
// Metadata
@@ -61,10 +63,9 @@ export default async function VaChapterPage({
const next =
currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
const chapterNum = (content as VaChapter).chapter;
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/books/va/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Vedānta-sūtra Transcript" })} />
<PageHeader sticky>
<Link
href="/books/va"
@@ -72,9 +73,12 @@ export default async function VaChapterPage({
>
&larr; Vaiśiṣṭyāṣṭakam
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{content.title}
</h1>
<div className="mt-3 flex items-start justify-between gap-3">
<h1 className="text-2xl font-bold text-foreground">
{content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader>
<div className="max-w-5xl mx-auto flex">
@@ -88,7 +92,7 @@ export default async function VaChapterPage({
currentSlug={slug}
/>
<Card>
<Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{content.paragraphs.map((paragraph, i) => (
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getVaMeta } from "@/lib/va";
import { ogForBook } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { bookJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function VaPage() {
return (
<div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/va", name: "Vedānta-sūtra Transcript" })} />
<PageHeader>
<Link
href="/books"
@@ -5,6 +5,7 @@ import Link from "next/link";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { UserAvatar } from "@/components/ui/user-avatar";
import { cn } from "@/lib/utils";
import {
Search,
@@ -32,6 +33,14 @@ export type DiscipleSummary = {
isVerified: boolean;
};
/** A disciple who has a platform account (linked by an admin). */
export type DiscipleLink = {
discipleId: string;
handle: string;
name: string;
avatarURL: string | null;
};
export type LetterCount = { letter: string; count: number };
export type PlaceGroup = { name: string; count: number };
export type YearGroup = { year: number; count: number };
@@ -49,6 +58,9 @@ type Props = {
years: YearGroup[];
totalCount: number;
filteredCount: number;
/** Disciples who have joined the platform — their cards are highlighted and
* link through to the profile. Small set (dozens at most). */
links: DiscipleLink[];
};
type SortMode = "name-asc" | "name-desc" | "date-asc" | "date-desc" | "place";
@@ -306,6 +318,97 @@ function Pagination({
);
}
// ---------------------------------------------------------------------------
// Disciple card
// ---------------------------------------------------------------------------
/**
* One disciple in the grid.
*
* When `link` is set the disciple has joined the platform, and the card is
* highlighted and becomes a link to their profile. The highlight is drawn
* entirely from theme tokens (`--primary`, `--ring`) rather than fixed colours,
* so it picks up whichever palette the reader has chosen — peacock green under
* Tulasī Garden, deep indigo under Yamunā Mist, and so on.
*/
function DiscipleCard({
disciple,
link,
}: {
disciple: DiscipleSummary;
link?: DiscipleLink;
}) {
const locationYear = formatLocationYear(
disciple.initiationPlace,
disciple.initiationDate,
);
const card = (
<Card
size="sm"
className={cn(
"h-full transition-all",
link
? // On the platform: tinted surface + primary ring so it reads as
// "one of us" at a glance across the grid.
"bg-primary/[0.06] ring-2 ring-primary/40 hover:ring-primary/70 hover:shadow-md"
: "hover:shadow-md hover:ring-foreground/20",
)}
>
<CardContent className="py-3 px-4 space-y-1.5">
<div className="flex items-start justify-between gap-2">
<p
className={cn(
"font-semibold leading-snug truncate",
link ? "text-primary" : "text-foreground",
)}
>
{disciple.initiatedName}
</p>
{disciple.suffix && (
<Badge
variant={link ? "default" : "secondary"}
className="shrink-0 text-[10px]"
>
{disciple.suffix}
</Badge>
)}
</div>
{locationYear && (
<p className="text-xs text-muted-foreground truncate">
{locationYear}
</p>
)}
{link && (
<div className="flex items-center gap-1.5 pt-0.5">
<UserAvatar
src={link.avatarURL}
name={link.name}
className="size-5"
textClassName="text-[9px]"
/>
<span className="truncate text-xs font-medium text-primary">
@{link.handle}
</span>
</div>
)}
</CardContent>
</Card>
);
if (!link) return card;
return (
<Link
href={`/u/${link.handle}`}
aria-label={`${disciple.initiatedName} — view @${link.handle}'s profile`}
className="block rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
{card}
</Link>
);
}
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
@@ -318,6 +421,7 @@ export function DisciplesBrowser({
suffixCounts,
totalCount,
filteredCount,
links,
}: Props) {
const [query, setQuery] = useState("");
const [sort, setSort] = useState<SortMode>("name-asc");
@@ -334,6 +438,13 @@ export function DisciplesBrowser({
setPage(1);
};
// Disciple id → their platform account, for the highlight + profile link.
const linkByDiscipleId = useMemo(() => {
const map = new Map<string, DiscipleLink>();
for (const l of links) map.set(l.discipleId, l);
return map;
}, [links]);
// Build a lookup map for letter counts
const countByLetter = useMemo(() => {
const map = new Map<string, number>();
@@ -539,41 +650,13 @@ export function DisciplesBrowser({
</div>
) : (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{pageData.map((disciple) => {
const locationYear = formatLocationYear(
disciple.initiationPlace,
disciple.initiationDate,
);
return (
<Card
key={disciple.id}
size="sm"
className="transition-all hover:shadow-md hover:ring-foreground/20"
>
<CardContent className="py-3 px-4 space-y-1.5">
<div className="flex items-start justify-between gap-2">
<p className="font-semibold text-foreground leading-snug truncate">
{disciple.initiatedName}
</p>
{disciple.suffix && (
<Badge
variant="secondary"
className="shrink-0 text-[10px]"
>
{disciple.suffix}
</Badge>
)}
</div>
{locationYear && (
<p className="text-xs text-muted-foreground truncate">
{locationYear}
</p>
)}
</CardContent>
</Card>
);
})}
{pageData.map((disciple) => (
<DiscipleCard
key={disciple.id}
disciple={disciple}
link={linkByDiscipleId.get(disciple.id)}
/>
))}
</div>
)}
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { ogForDisciples } from "@/lib/og";
import {
getTotalDiscipleCount,
@@ -12,6 +12,7 @@ import {
getDiscipleYears,
getDiscipleSuffixCounts,
getFilteredDiscipleCount,
getDiscipleLinks,
} from "@/lib/disciples";
import type { SuffixFilter } from "@/lib/disciples";
import { DisciplesBrowser } from "./disciples-browser";
@@ -62,6 +63,7 @@ export default async function DisciplesPage({
places,
years,
suffixCounts,
links,
] = await Promise.all([
getTotalDiscipleCount(),
getFilteredDisciples(letterFilter, activeSuffix),
@@ -70,6 +72,7 @@ export default async function DisciplesPage({
getDisciplePlaces(),
getDiscipleYears(),
getDiscipleSuffixCounts(),
getDiscipleLinks(),
]);
// Serialize dates to ISO strings for the client component
@@ -105,6 +108,7 @@ export default async function DisciplesPage({
years={years}
totalCount={totalCount}
filteredCount={filteredCount}
links={links}
/>
</main>
</div>
@@ -4,12 +4,14 @@ import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { ogForEssay, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
import { bff, bffList } from "@/lib/bff";
// ---------------------------------------------------------------------------
@@ -119,6 +121,7 @@ export default async function EssayPage({
return (
<div className="min-h-screen bg-background">
<JsonLd data={articleJsonLd({ path: `/essays/${slug}`, headline: essay.title, description: essay.paragraphs?.[0], isPartOf: "Essays & Talks" })} />
<PageHeader sticky>
<Link
href="/essays"
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { PageHeader } from "@/components/layout/page-header";
import { buildOgMeta } from "@/lib/og";
import { bffList } from "@/lib/bff";
import { EssaysGrid } from "./essays-grid";
@@ -5,6 +5,8 @@ import { notFound } from "next/navigation";
import { getAllGlossaryEntries } from "@/lib/api";
import { buildOgMeta, extractQuoteFromHl } from "@/lib/og";
import { GlossaryClient } from "../glossary-client";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
// ---------------------------------------------------------------------------
// Diacritic → base letter mapping
@@ -145,6 +147,13 @@ export default async function GlossaryLetterPage({
return (
<div className="min-h-screen bg-background">
<JsonLd
data={articleJsonLd({
path: `/glossary/${rawLetter}`,
headline: `Glossary — ${activeLetter}`,
isPartOf: "Glossary of Sanskrit & Vedic Terms",
})}
/>
<GlossaryClient
entries={clientEntries}
activeLetter={activeLetter}
@@ -1,14 +1,13 @@
"use client";
import { useState, useMemo, useRef, useEffect, useCallback } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useSearchParams } from "next/navigation";
import Link from "next/link";
import { Search, X, Menu, ChevronLeft, ChevronRight } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Card, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { Button, buttonVariants } from "@/components/ui/button";
import { ReadingSettings } from "@/components/reading-settings";
import {
Drawer,
DrawerClose,
@@ -290,7 +289,6 @@ export function GlossaryClient({
letters,
letterCounts,
}: Props) {
const router = useRouter();
const searchParams = useSearchParams();
const [query, setQuery] = useState("");
@@ -389,22 +387,17 @@ export function GlossaryClient({
<>
{/* Sticky header */}
<header className="border-b border-border bg-card px-6 py-4 sticky top-0 z-20">
<div className="max-w-5xl mx-auto flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<Link
href="/"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; Home
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
<span className="text-primary">Glossary: </span>
{activeEntry?.term || activeLetter}
</h1>
</div>
<div className="shrink-0 pt-1">
<ReadingSettings />
</div>
<div className="max-w-5xl mx-auto">
<Link
href="/"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; Home
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
<span className="text-primary">Glossary: </span>
{activeEntry?.term || activeLetter}
</h1>
</div>
</header>

Some files were not shown because too many files have changed in this diff Show More