diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md
index 154dda83..2731b12f 100644
--- a/PROJECT_CONTEXT.md
+++ b/PROJECT_CONTEXT.md
@@ -251,6 +251,9 @@ model GlossaryEntry {
| SB First Canto (1962 Edition) | `sb1962` | 762 | 19 | — |
| Śrī Īśopaniṣad | `iso` | 19 | — | Invocation + 18 mantras |
| The Nectar of Instruction | `noi` | 11 | — | 11 texts |
+| Śrī Brahma-saṁhitā | `bs` | 62 | 1 (Ch. 5) | — |
+| Mukunda-mālā-stotra | `mm` | 6 | 1 | — |
+| Nārada-bhakti-sūtra | `nbs` | 8 | 1 | — |
### Prose Books (stored in `prose_chapters` collection)
@@ -272,13 +275,40 @@ model GlossaryEntry {
| The Kṛṣṇa Consciousness Handbook | `hkh` | 19 | other.html |
| The Science of Self-Realization | `ssr` | 50 sub-chapters (8 parents) | other.html |
| Dialectic Spiritualism | `ds` | 36 sub-chapters (13 parents) | other.html |
+| Life Comes from Life | `lcl` | 16 | other.html |
+| Light of the Bhāgavata | `lob` | 48 | other.html |
+| Teachings of Lord Kapila | `tlk` | 18 | other.html |
+| Teachings of Queen Kuntī | `tqk` | 26 | other.html |
+| Gītār Gān | `gg` | 18 | other.html (Bengali poetic BG) |
+| Songs of the Vaiṣṇava Ācāryas | `sva` | 67 songs (4 parts, hierarchical) | other.html |
+| Message of Godhead | `mog` | 3 | others-before-1977.html |
+| Śrī Caitanya-bhāgavata Commentary | `cbhc` | 4 | others-before-1977.html |
+| Bhagavāner Kathā | `bk` | 2 | others-before-1977.html (ScaGoudy) |
+| Prayers of King Kulaśekhara (1958) | `pk` | 6 | others-before-1977.html |
### Essays (stored in `essays` collection)
-| Essay | Slug | Group |
-|-------|------|-------|
-| Krishna The Reservoir Of Pleasure | `krishna-reservoir-of-pleasure` | `two-essays` |
-| Who Is Crazy? | `who-is-crazy` | `two-essays` |
+| Essay | Slug | Group | Year |
+|-------|------|-------|------|
+| Krishna The Reservoir Of Pleasure | `krishna-reservoir-of-pleasure` | `two-essays` | 1966 |
+| Who Is Crazy? | `who-is-crazy` | `two-essays` | 1966 |
+| The Spiritual Masters | `the-spiritual-masters` | `cc-essays` | 1960 |
+| The Absolute Truth | `the-absolute-truth` | `cc-essays` | 1957 |
+| The Causes of Lord Caitanya's Incarnation | `causes-of-lord-caitanyas-incarnation` | `cc-essays` | 1957 |
+| The Confidential Reasons for Lord Caitanya's Appearance | `confidential-reasons-for-lord-caitanyas-appearance` | `cc-essays` | 1957 |
+| The Glories of Lord Nityānanda | `glories-of-lord-nityananda` | `cc-essays` | 1957 |
+| Vedānta-sūtra (Typed Transcript) | `vedanta-sutra-typed` | `typed-transcripts` | 1969 |
+| Shaktipujah | `shaktipujah` | `reviews` | — |
+| Devotee and the Divinity | `devotee-and-the-divinity` | `standalone` | — |
+
+### Other Documents (stored in `book_meta` + `prose_chapters` with `od-` prefix)
+
+| Document | Code | Chapters |
+|----------|------|----------|
+| An Analysis of Bhagwat Geeta | `od-analysis-bg` | 3 |
+| Solution of Present Crisis by Bhagwat Geeta | `od-solution-crisis` | 4 |
+| Śrī Caitanya Mahāprabhu | `od-sri-caitanya` | 3 |
+| Conception of Geeta Nagari on the Principles of Gandhi | `od-geeta-nagari` | 1 |
### Other
@@ -315,6 +345,11 @@ IAST transliteration → Devanagari via `indic-transliteration` library. Key fix
- **TLC (1968 Edition)** — plain English transliterations ("Krishna" not "Kṛṣṇa")
- **Two Essays (1966)** — plain English
+- **GG (Gītār Gān)** — Bengali poetic text (ScaGoudy decoded for transliteration)
+- **LCL (Life Comes from Life)** — dialogue book, ScaGoudy decoded
+- **MOG, CBHC, PK** — pre-1977 handwritten documents, plain English
+- **BK (Bhagavāner Kathā)** — pre-1977, uses ScaGoudy encoding
+- **CC Essays, Vedānta-sūtra transcript** — plain English
- All other books from other.html use ScaGoudy encoding
---
@@ -331,13 +366,16 @@ IAST transliteration → Devanagari via `indic-transliteration` library. Key fix
| `/books/sb1962/[chapter]/[verse]` | SB1962 |
| `/books/iso/[mantra]` | Īśopaniṣad (verse + intro segments) |
| `/books/noi/[text]` | Nectar of Instruction (verse + intro segments) |
+| `/books/bs/[chapter]/[verse]` | Brahma-saṁhitā (chapter 5, 62 verses) |
+| `/books/mm/[chapter]/[verse]` | Mukunda-mālā-stotra (6 mantras) |
+| `/books/nbs/[chapter]/[verse]` | Nārada-bhakti-sūtra (8 sūtras) |
### Prose books (use per-book data layers + reading pages)
| Route Pattern | Books |
|---------------|-------|
| `/books/kb/[volume]/[slug]` | Kṛṣṇa Book |
-| `/books/{code}/[chapter]` | NoD, TLC, EJ, EK, MG, BBD, OWK, RV, POY, PQPA, TTP, OCHKM, HKH |
+| `/books/{code}/[chapter]` | NoD, TLC, EJ, EK, MG, BBD, OWK, RV, POY, PQPA, TTP, OCHKM, HKH, LCL, LOB, TLK, TQK, GG, MOG, CBHC, BK, PK |
### Hierarchical prose books (sub-chapters as separate pages)
@@ -345,14 +383,23 @@ IAST transliteration → Devanagari via `indic-transliteration` library. Key fix
|---------------|-------|
| `/books/ssr/[chapter]` | Science of Self-Realization (slugs: "1-1", "1-2", "intro-foreword", etc.) |
| `/books/ds/[chapter]` | Dialectic Spiritualism (slugs: "1-1" = Socrates, "1-2" = Plato, etc.) |
+| `/books/sva/[chapter]` | Songs of the Vaiṣṇava Ācāryas (slugs: "1-1" = Śrī Guru Praṇāma, etc.) |
### Essays
| Route | Content |
|-------|---------|
-| `/essays` | All essays listing |
+| `/essays` | All essays listing (10 essays across 4 groups) |
| `/essays/[slug]` | Individual essay reader |
+### Other Documents
+
+| Route | Content |
+|-------|---------|
+| `/other-documents` | All other documents listing |
+| `/other-documents/[slug]` | Document index (chapter listing) |
+| `/other-documents/[slug]/[chapter]` | Individual chapter reader |
+
### Other routes
| Route | Content |
diff --git a/app/books/bk/[chapter]/page.tsx b/app/books/bk/[chapter]/page.tsx
new file mode 100644
index 00000000..43ce392c
--- /dev/null
+++ b/app/books/bk/[chapter]/page.tsx
@@ -0,0 +1,249 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getBkContent,
+ getBkSidebarItems,
+ type BkChapter,
+ type BkSidebarItem,
+} from "@/lib/bk";
+import { PageHeader } from "@/components/page-header";
+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";
+
+export const dynamic = "force-dynamic";
+
+// ---------------------------------------------------------------------------
+// Page
+// ---------------------------------------------------------------------------
+
+export default async function BkChapterPage({
+ params,
+}: {
+ params: Promise<{ chapter: string }>;
+}) {
+ const { chapter: slug } = await params;
+
+ const content = await getBkContent(slug);
+ if (!content) notFound();
+
+ const sidebarItems = await getBkSidebarItems();
+ const currentIdx = sidebarItems.findIndex((item) => item.slug === slug);
+
+ const prev = currentIdx > 0 ? sidebarItems[currentIdx - 1] : null;
+ const next =
+ currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
+
+ // Build the page title
+ const chapterNum = (content as BkChapter).chapter ?? null;
+
+ return (
+
+
+
+ ← Bhagavāner Kathā
+
+
+ {chapterNum != null && (
+ Chapter {chapterNum}:
+ )}
+ {content.title}
+
+
+
+
+ {/* Left sidebar */}
+
+
+ {/* Main content */}
+
+ {/* Top navigation */}
+
+
+ {/* Content */}
+
+
+
+ {content.paragraphs.map((paragraph, i) => (
+
+ ))}
+
+
+
+
+ {/* Bottom navigation */}
+
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Sidebar
+// ---------------------------------------------------------------------------
+
+function BkSidebar({
+ items,
+ currentSlug,
+}: {
+ items: BkSidebarItem[];
+ currentSlug: string;
+}) {
+ const chapterItems = items.filter((item) => item.type === "chapter");
+
+ return (
+
+ {/* Chapters */}
+ {chapterItems.length > 0 && (
+ <>
+
+ Chapters
+
+
+ {chapterItems.map((item) => (
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+function SidebarLink({
+ item,
+ isActive,
+}: {
+ item: BkSidebarItem;
+ isActive: boolean;
+}) {
+ let label: string;
+ if (item.type === "chapter" && item.chapter != null) {
+ // Extract just the title portion after "Chapter N: "
+ const titlePart = item.label.replace(/^Chapter \d+:\s*/, "");
+ label = `${item.chapter}. ${titlePart}`;
+ } else {
+ label = item.label;
+ }
+
+ return (
+
+ {label}
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Prev / Next navigation
+// ---------------------------------------------------------------------------
+
+function BkNav({
+ prev,
+ next,
+ sidebarItems,
+ currentSlug,
+}: {
+ prev: BkSidebarItem | null;
+ next: BkSidebarItem | null;
+ sidebarItems: BkSidebarItem[];
+ currentSlug: string;
+}) {
+ const mobileNavGroups: MobileNavGroup[] = [];
+
+ const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
+ if (chapterItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Chapters",
+ items: chapterItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/books/bk/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ return (
+
+
+
+ {prev ? (
+
+
+
+ {prev.type === "chapter" && prev.chapter != null
+ ? `Ch. ${prev.chapter}`
+ : prev.label}
+
+
Previous
+
+ ) : (
+
+ )}
+
+
+ {next ? (
+
+
+ {next.type === "chapter" && next.chapter != null
+ ? `Ch. ${next.chapter}`
+ : next.label}
+
+
Next
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/books/bk/page.tsx b/app/books/bk/page.tsx
new file mode 100644
index 00000000..853d0eda
--- /dev/null
+++ b/app/books/bk/page.tsx
@@ -0,0 +1,66 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { getBkMeta } from "@/lib/bk";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent } from "@/components/ui/card";
+
+export default async function BkPage() {
+ const meta = await getBkMeta();
+ if (!meta) notFound();
+
+ return (
+
+
+
+ ← Books
+
+
+ {meta.title}
+
+ {meta.author}
+
+
+
+ {/* Description */}
+ {meta.description && (
+ {meta.description}
+ )}
+
+ {/* Chapters */}
+
+
+ Chapters
+
+
+
+
+ {meta.chapters.map((ch) => (
+
+
+
+
+ Chapter {ch.number}
+
+
+ {ch.title}
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/app/books/cbhc/[chapter]/page.tsx b/app/books/cbhc/[chapter]/page.tsx
new file mode 100644
index 00000000..1e194a11
--- /dev/null
+++ b/app/books/cbhc/[chapter]/page.tsx
@@ -0,0 +1,249 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getCbhcContent,
+ getCbhcSidebarItems,
+ type CbhcChapter,
+ type CbhcSidebarItem,
+} from "@/lib/cbhc";
+import { PageHeader } from "@/components/page-header";
+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";
+
+export const dynamic = "force-dynamic";
+
+// ---------------------------------------------------------------------------
+// Page
+// ---------------------------------------------------------------------------
+
+export default async function CbhcChapterPage({
+ params,
+}: {
+ params: Promise<{ chapter: string }>;
+}) {
+ const { chapter: slug } = await params;
+
+ const content = await getCbhcContent(slug);
+ if (!content) notFound();
+
+ const sidebarItems = await getCbhcSidebarItems();
+ const currentIdx = sidebarItems.findIndex((item) => item.slug === slug);
+
+ const prev = currentIdx > 0 ? sidebarItems[currentIdx - 1] : null;
+ const next =
+ currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
+
+ // Build the page title
+ const chapterNum = (content as CbhcChapter).chapter ?? null;
+
+ return (
+
+
+
+ ← Śrī Caitanya-bhāgavata Commentary
+
+
+ {chapterNum != null && (
+ Chapter {chapterNum}:
+ )}
+ {content.title}
+
+
+
+
+ {/* Left sidebar */}
+
+
+ {/* Main content */}
+
+ {/* Top navigation */}
+
+
+ {/* Content */}
+
+
+
+ {content.paragraphs.map((paragraph, i) => (
+
+ ))}
+
+
+
+
+ {/* Bottom navigation */}
+
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Sidebar
+// ---------------------------------------------------------------------------
+
+function CbhcSidebar({
+ items,
+ currentSlug,
+}: {
+ items: CbhcSidebarItem[];
+ currentSlug: string;
+}) {
+ const chapterItems = items.filter((item) => item.type === "chapter");
+
+ return (
+
+ {/* Chapters */}
+ {chapterItems.length > 0 && (
+ <>
+
+ Chapters
+
+
+ {chapterItems.map((item) => (
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+function SidebarLink({
+ item,
+ isActive,
+}: {
+ item: CbhcSidebarItem;
+ isActive: boolean;
+}) {
+ let label: string;
+ if (item.type === "chapter" && item.chapter != null) {
+ // Extract just the title portion after "Chapter N: "
+ const titlePart = item.label.replace(/^Chapter \d+:\s*/, "");
+ label = `${item.chapter}. ${titlePart}`;
+ } else {
+ label = item.label;
+ }
+
+ return (
+
+ {label}
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Prev / Next navigation
+// ---------------------------------------------------------------------------
+
+function CbhcNav({
+ prev,
+ next,
+ sidebarItems,
+ currentSlug,
+}: {
+ prev: CbhcSidebarItem | null;
+ next: CbhcSidebarItem | null;
+ sidebarItems: CbhcSidebarItem[];
+ currentSlug: string;
+}) {
+ const mobileNavGroups: MobileNavGroup[] = [];
+
+ const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
+ if (chapterItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Chapters",
+ items: chapterItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/books/cbhc/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ return (
+
+
+
+ {prev ? (
+
+
+
+ {prev.type === "chapter" && prev.chapter != null
+ ? `Ch. ${prev.chapter}`
+ : prev.label}
+
+
Previous
+
+ ) : (
+
+ )}
+
+
+ {next ? (
+
+
+ {next.type === "chapter" && next.chapter != null
+ ? `Ch. ${next.chapter}`
+ : next.label}
+
+
Next
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/books/cbhc/page.tsx b/app/books/cbhc/page.tsx
new file mode 100644
index 00000000..98b1a27f
--- /dev/null
+++ b/app/books/cbhc/page.tsx
@@ -0,0 +1,66 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { getCbhcMeta } from "@/lib/cbhc";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent } from "@/components/ui/card";
+
+export default async function CbhcPage() {
+ const meta = await getCbhcMeta();
+ if (!meta) notFound();
+
+ return (
+
+
+
+ ← Books
+
+
+ {meta.title}
+
+ {meta.author}
+
+
+
+ {/* Description */}
+ {meta.description && (
+ {meta.description}
+ )}
+
+ {/* Chapters */}
+
+
+ Chapters
+
+
+
+
+ {meta.chapters.map((ch) => (
+
+
+
+
+ Chapter {ch.number}
+
+
+ {ch.title}
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/app/books/mog/[chapter]/page.tsx b/app/books/mog/[chapter]/page.tsx
new file mode 100644
index 00000000..0d15baa7
--- /dev/null
+++ b/app/books/mog/[chapter]/page.tsx
@@ -0,0 +1,276 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getMogContent,
+ getMogSidebarItems,
+ type MogChapter,
+ type MogSidebarItem,
+} from "@/lib/mog";
+import { PageHeader } from "@/components/page-header";
+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";
+
+export const dynamic = "force-dynamic";
+
+// ---------------------------------------------------------------------------
+// Page
+// ---------------------------------------------------------------------------
+
+export default async function MogChapterPage({
+ params,
+}: {
+ params: Promise<{ chapter: string }>;
+}) {
+ const { chapter: slug } = await params;
+
+ const content = await getMogContent(slug);
+ if (!content) notFound();
+
+ const sidebarItems = await getMogSidebarItems();
+ const currentIdx = sidebarItems.findIndex((item) => item.slug === slug);
+
+ const prev = currentIdx > 0 ? sidebarItems[currentIdx - 1] : null;
+ const next =
+ currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
+
+ // Build the page title
+ const isChapter =
+ "chapter" in content &&
+ typeof (content as MogChapter).chapter === "number" &&
+ (content as MogChapter).chapter !== null;
+ const chapterNum = isChapter ? (content as MogChapter).chapter : null;
+
+ return (
+
+
+
+ ← Message of Godhead
+
+
+ {chapterNum != null && (
+ Chapter {chapterNum}:
+ )}
+ {content.title}
+
+
+
+
+ {/* Left sidebar */}
+
+
+ {/* Main content */}
+
+ {/* Top navigation */}
+
+
+ {/* Content */}
+
+
+
+ {content.paragraphs.map((paragraph, i) => (
+
+ ))}
+
+
+
+
+ {/* Bottom navigation */}
+
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Sidebar
+// ---------------------------------------------------------------------------
+
+function MogSidebar({
+ items,
+ currentSlug,
+}: {
+ items: MogSidebarItem[];
+ currentSlug: string;
+}) {
+ const introItems = items.filter((item) => item.type === "intro-segment");
+ const chapterItems = items.filter((item) => item.type === "chapter");
+
+ return (
+
+ {/* Front Matter */}
+ {introItems.length > 0 && (
+ <>
+
+ Front Matter
+
+
+ {introItems.map((item) => (
+
+ ))}
+
+ >
+ )}
+
+ {/* Chapters */}
+ {chapterItems.length > 0 && (
+ <>
+
+ Chapters
+
+
+ {chapterItems.map((item) => (
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+function SidebarLink({
+ item,
+ isActive,
+}: {
+ item: MogSidebarItem;
+ isActive: boolean;
+}) {
+ return (
+
+ {item.label}
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Prev / Next navigation
+// ---------------------------------------------------------------------------
+
+function MogNav({
+ prev,
+ next,
+ sidebarItems,
+ currentSlug,
+}: {
+ prev: MogSidebarItem | null;
+ next: MogSidebarItem | null;
+ sidebarItems: MogSidebarItem[];
+ currentSlug: string;
+}) {
+ const mobileNavGroups: MobileNavGroup[] = [];
+
+ const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
+ if (introItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Front Matter",
+ items: introItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/books/mog/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
+ if (chapterItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Chapters",
+ items: chapterItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/books/mog/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ return (
+
+
+
+ {prev ? (
+
+
+
+ {prev.type === "chapter" && prev.chapter != null
+ ? `Ch. ${prev.chapter}`
+ : prev.label}
+
+
Previous
+
+ ) : (
+
+ )}
+
+
+ {next ? (
+
+
+ {next.type === "chapter" && next.chapter != null
+ ? `Ch. ${next.chapter}`
+ : next.label}
+
+
Next
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/books/mog/page.tsx b/app/books/mog/page.tsx
new file mode 100644
index 00000000..11611c97
--- /dev/null
+++ b/app/books/mog/page.tsx
@@ -0,0 +1,100 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { getMogMeta, getMogIntroSegments } from "@/lib/mog";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent } from "@/components/ui/card";
+
+export default async function MogPage() {
+ const meta = await getMogMeta();
+ if (!meta) notFound();
+
+ const introSegments = await getMogIntroSegments();
+
+ return (
+
+
+
+ ← Books
+
+
+ {meta.title}
+
+ {meta.author}
+
+
+
+ {/* Description */}
+ {meta.description && (
+ {meta.description}
+ )}
+
+ {/* Front Matter */}
+ {introSegments && introSegments.length > 0 && (
+
+
+ Front Matter
+
+
+
+
+ {introSegments.map((seg) => (
+
+
+
+
+ {seg.paragraphs.length} paragraphs
+
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Chapters */}
+
+
+ Chapters
+
+
+
+
+ {meta.chapters.map((ch) => (
+
+
+
+
+ Chapter {ch.number}
+
+
+ {ch.title}
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/app/books/pk/[chapter]/page.tsx b/app/books/pk/[chapter]/page.tsx
new file mode 100644
index 00000000..cefed86e
--- /dev/null
+++ b/app/books/pk/[chapter]/page.tsx
@@ -0,0 +1,239 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getPkContent,
+ getPkSidebarItems,
+ type PkChapter,
+ type PkSidebarItem,
+} from "@/lib/pk";
+import { PageHeader } from "@/components/page-header";
+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";
+
+export const dynamic = "force-dynamic";
+
+// ---------------------------------------------------------------------------
+// Page
+// ---------------------------------------------------------------------------
+
+export default async function PkChapterPage({
+ params,
+}: {
+ params: Promise<{ chapter: string }>;
+}) {
+ const { chapter: slug } = await params;
+
+ const content = await getPkContent(slug);
+ if (!content) notFound();
+
+ const sidebarItems = await getPkSidebarItems();
+ const currentIdx = sidebarItems.findIndex((item) => item.slug === slug);
+
+ const prev = currentIdx > 0 ? sidebarItems[currentIdx - 1] : null;
+ const next =
+ currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
+
+ const chapterNum = (content as PkChapter).chapter;
+
+ return (
+
+
+
+ ← Prayers of King Kulaśekhara
+
+
+ {chapterNum != null && (
+ Verse {chapterNum}:
+ )}
+ {content.title}
+
+
+
+
+ {/* Left sidebar */}
+
+
+ {/* Main content */}
+
+ {/* Top navigation */}
+
+
+ {/* Content */}
+
+
+
+ {content.paragraphs.map((paragraph, i) => (
+
+ ))}
+
+
+
+
+ {/* Bottom navigation */}
+
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Sidebar
+// ---------------------------------------------------------------------------
+
+function PkSidebar({
+ items,
+ currentSlug,
+}: {
+ items: PkSidebarItem[];
+ currentSlug: string;
+}) {
+ const chapterItems = items.filter((item) => item.type === "chapter");
+
+ return (
+
+ {/* Chapters (Prayers) */}
+ {chapterItems.length > 0 && (
+ <>
+
+ Prayers
+
+
+ {chapterItems.map((item) => (
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+function SidebarLink({
+ item,
+ isActive,
+}: {
+ item: PkSidebarItem;
+ isActive: boolean;
+}) {
+ return (
+
+ {item.label}
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Prev / Next navigation
+// ---------------------------------------------------------------------------
+
+function PkNav({
+ prev,
+ next,
+ sidebarItems,
+ currentSlug,
+}: {
+ prev: PkSidebarItem | null;
+ next: PkSidebarItem | null;
+ sidebarItems: PkSidebarItem[];
+ currentSlug: string;
+}) {
+ const mobileNavGroups: MobileNavGroup[] = [];
+
+ const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
+ if (chapterItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Prayers",
+ items: chapterItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/books/pk/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ return (
+
+
+
+ {prev ? (
+
+
+
+ {prev.chapter != null
+ ? `Prayer ${prev.chapter}`
+ : prev.label}
+
+
Previous
+
+ ) : (
+
+ )}
+
+
+ {next ? (
+
+
+ {next.chapter != null
+ ? `Prayer ${next.chapter}`
+ : next.label}
+
+
Next
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/books/pk/page.tsx b/app/books/pk/page.tsx
new file mode 100644
index 00000000..8753b8eb
--- /dev/null
+++ b/app/books/pk/page.tsx
@@ -0,0 +1,66 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { getPkMeta } from "@/lib/pk";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent } from "@/components/ui/card";
+
+export default async function PkPage() {
+ const meta = await getPkMeta();
+ if (!meta) notFound();
+
+ return (
+
+
+
+ ← Books
+
+
+ {meta.title}
+
+ {meta.author}
+
+
+
+ {/* Description */}
+ {meta.description && (
+ {meta.description}
+ )}
+
+ {/* Chapters (Prayers) */}
+
+
+ Prayers
+
+
+
+
+ {meta.chapters.map((ch) => (
+
+
+
+
+ Verse {ch.number}
+
+
+ {ch.title}
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/app/books/va/[chapter]/page.tsx b/app/books/va/[chapter]/page.tsx
new file mode 100644
index 00000000..32f5806e
--- /dev/null
+++ b/app/books/va/[chapter]/page.tsx
@@ -0,0 +1,192 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getVaContent,
+ getVaSidebarItems,
+ type VaChapter,
+ type VaSidebarItem,
+} from "@/lib/va";
+import { PageHeader } from "@/components/page-header";
+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";
+
+export default async function VaChapterPage({
+ params,
+}: {
+ params: Promise<{ chapter: string }>;
+}) {
+ const { chapter: slug } = await params;
+
+ const content = await getVaContent(slug);
+ if (!content) notFound();
+
+ const sidebarItems = await getVaSidebarItems();
+ const currentIdx = sidebarItems.findIndex((item) => item.slug === slug);
+
+ const prev = currentIdx > 0 ? sidebarItems[currentIdx - 1] : null;
+ const next =
+ currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
+
+ const chapterNum = (content as VaChapter).chapter;
+
+ return (
+
+
+
+ ← Vaiśiṣṭyāṣṭakam
+
+
+ {content.title}
+
+
+
+
+
+
+
+
+
+
+
+
+ {content.paragraphs.map((paragraph, i) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+}
+
+function VaSidebar({
+ items,
+ currentSlug,
+}: {
+ items: VaSidebarItem[];
+ currentSlug: string;
+}) {
+ return (
+
+
+ Chapters
+
+
+ {items.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ );
+}
+
+function VaNav({
+ prev,
+ next,
+ sidebarItems,
+ currentSlug,
+}: {
+ prev: VaSidebarItem | null;
+ next: VaSidebarItem | null;
+ sidebarItems: VaSidebarItem[];
+ currentSlug: string;
+}) {
+ const mobileNavGroups: MobileNavGroup[] = [];
+
+ if (sidebarItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Chapters",
+ items: sidebarItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/books/va/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ return (
+
+
+
+ {prev ? (
+
+
+
+ {prev.label}
+
+
Previous
+
+ ) : (
+
+ )}
+
+
+ {next ? (
+
+
+ {next.label}
+
+
Next
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/books/va/page.tsx b/app/books/va/page.tsx
new file mode 100644
index 00000000..c4caec70
--- /dev/null
+++ b/app/books/va/page.tsx
@@ -0,0 +1,61 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { getVaMeta } from "@/lib/va";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent } from "@/components/ui/card";
+
+export default async function VaPage() {
+ const meta = await getVaMeta();
+ if (!meta) notFound();
+
+ return (
+
+
+
+ ← Books
+
+
+ {meta.title}
+
+ {meta.author}
+
+
+
+ {meta.description && (
+ {meta.description}
+ )}
+
+
+
+ Chapters
+
+
+
+
+ {meta.chapters.map((ch) => (
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/app/essays/essays-grid.tsx b/app/essays/essays-grid.tsx
new file mode 100644
index 00000000..af6f5c26
--- /dev/null
+++ b/app/essays/essays-grid.tsx
@@ -0,0 +1,144 @@
+"use client";
+
+import { useState, useMemo } from "react";
+import Link from "next/link";
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardContent,
+} from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Input } from "@/components/ui/input";
+import { cn } from "@/lib/utils";
+import { Search } from "lucide-react";
+
+export type EssayInfo = {
+ slug: string;
+ title: string;
+ year: number | null;
+ group: string | null;
+ href: string;
+};
+
+/**
+ * Strip diacritics / accents so that "Krsna" matches "Kṛṣṇa",
+ * "Gita" matches "Gītā", "Srimad" matches "Śrīmad", etc.
+ */
+function normalize(text: string): string {
+ return (
+ text
+ .toLowerCase()
+ .replace(/ṛ/g, "r")
+ .replace(/ṝ/g, "r")
+ .replace(/ṣ/g, "s")
+ .replace(/ś/g, "s")
+ .replace(/ṭ/g, "t")
+ .replace(/ḍ/g, "d")
+ .replace(/ṇ/g, "n")
+ .replace(/ñ/g, "n")
+ .replace(/ṅ/g, "n")
+ .replace(/ḥ/g, "h")
+ .replace(/ṁ/g, "m")
+ .replace(/ṃ/g, "m")
+ .replace(/ḷ/g, "l")
+ .normalize("NFD")
+ .replace(/[\u0300-\u036f]/g, "")
+ );
+}
+
+/** Human-readable group labels */
+const GROUP_LABELS: Record = {
+ "cc-essays": "Śrī Caitanya-caritāmṛta Essays",
+ "pre-1977-essays": "Pre-1977 Essays",
+ "two-essays": "Two Essays (1966)",
+ reviews: "Reviews",
+ standalone: "Standalone",
+ "typed-transcripts": "Typed Transcripts",
+};
+
+function groupLabel(group: string | null): string {
+ if (!group) return "Other";
+ return GROUP_LABELS[group] ?? group;
+}
+
+export function EssaysGrid({ essays }: { essays: EssayInfo[] }) {
+ const [query, setQuery] = useState("");
+
+ const normalizedEssays = useMemo(
+ () =>
+ essays.map((essay) => ({
+ essay,
+ searchText: normalize(
+ `${essay.title} ${essay.group ?? ""} ${essay.slug}`,
+ ),
+ })),
+ [essays],
+ );
+
+ const filtered = useMemo(() => {
+ if (!query.trim()) return essays;
+ const q = normalize(query.trim());
+ return normalizedEssays
+ .filter((entry) => entry.searchText.includes(q))
+ .map((entry) => entry.essay);
+ }, [essays, normalizedEssays, query]);
+
+ return (
+
+ {/* Search input */}
+
+
+ setQuery(e.target.value)}
+ className="pl-9"
+ />
+
+
+ {/* Results count when searching */}
+ {query.trim() && (
+
+ {filtered.length === 0
+ ? "No essays found"
+ : `${filtered.length} essay${filtered.length === 1 ? "" : "s"} found`}
+
+ )}
+
+ {/* Grid */}
+
+ {filtered.map((essay) => (
+
+
+
+
+ {essay.title}
+
+
+
+
+ {essay.year && (
+
+ {essay.year}
+
+ )}
+ {essay.group && (
+
+ {groupLabel(essay.group)}
+
+ )}
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/essays/page.tsx b/app/essays/page.tsx
index ecaf9436..4e6eaf40 100644
--- a/app/essays/page.tsx
+++ b/app/essays/page.tsx
@@ -2,19 +2,27 @@ export const dynamic = "force-dynamic";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
-import { Card, CardContent } from "@/components/ui/card";
import { prisma } from "@/lib/db";
+import { EssaysGrid } from "./essays-grid";
async function getAllEssays() {
return prisma.essay.findMany({
orderBy: [{ group: "asc" }, { order: "asc" }],
- select: { slug: true, title: true, author: true, year: true, group: true },
+ select: { slug: true, title: true, year: true, group: true },
});
}
export default async function EssaysPage() {
const essays = await getAllEssays();
+ const items = essays.map((essay) => ({
+ slug: essay.slug,
+ title: essay.title,
+ year: essay.year,
+ group: essay.group,
+ href: `/essays/${essay.slug}`,
+ }));
+
return (
@@ -30,31 +38,8 @@ export default async function EssaysPage() {
-
-
-
-
- {essays.map((essay) => (
-
-
-
-
- {essay.title}
-
-
- {essay.author}
- {essay.year ? ` · ${essay.year}` : ""}
-
-
-
-
- ))}
-
-
-
+
+
);
diff --git a/app/journals/[slug]/[chapter]/page.tsx b/app/journals/[slug]/[chapter]/page.tsx
new file mode 100644
index 00000000..dfe6cf9b
--- /dev/null
+++ b/app/journals/[slug]/[chapter]/page.tsx
@@ -0,0 +1,223 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getJournalContent,
+ getJournalMeta,
+ getJournalSidebarItems,
+ slugToBookCode,
+ type JournalSidebarItem,
+} from "@/lib/journals";
+import { PageHeader } from "@/components/page-header";
+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";
+
+export default async function JournalChapterPage({
+ params,
+}: {
+ params: Promise<{ slug: string; chapter: string }>;
+}) {
+ const { slug, chapter: chapterSlug } = await params;
+ const bookCode = slugToBookCode(slug);
+
+ const [meta, content, sidebarItems] = await Promise.all([
+ getJournalMeta(bookCode),
+ getJournalContent(bookCode, chapterSlug),
+ getJournalSidebarItems(bookCode),
+ ]);
+
+ if (!meta || !content) notFound();
+
+ // Filter out heading items for prev/next navigation
+ const navItems = sidebarItems.filter((item) => item.type !== "heading");
+ const currentIdx = navItems.findIndex((item) => item.slug === chapterSlug);
+ const prev = currentIdx > 0 ? navItems[currentIdx - 1] : null;
+ const next =
+ currentIdx < navItems.length - 1 ? navItems[currentIdx + 1] : null;
+
+ return (
+
+
+
+ ← {meta.title}
+
+
+ {content.title}
+
+
+
+
+
+
+
+
+
+
+
+
+ {content.paragraphs.map((paragraph, i) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+}
+
+function ChapterSidebar({
+ items,
+ currentSlug,
+ docSlug,
+}: {
+ items: JournalSidebarItem[];
+ currentSlug: string;
+ docSlug: string;
+}) {
+ return (
+
+
+ Chapters
+
+
+ {items.map((item, i) =>
+ item.type === "heading" ? (
+
+ {item.label}
+
+ ) : (
+
+ {item.label}
+
+ ),
+ )}
+
+
+ );
+}
+
+function ChapterNav({
+ prev,
+ next,
+ navItems,
+ currentSlug,
+ docSlug,
+ docTitle,
+ showMobileNav = false,
+}: {
+ prev: JournalSidebarItem | null;
+ next: JournalSidebarItem | null;
+ navItems: JournalSidebarItem[];
+ currentSlug: string;
+ docSlug: string;
+ docTitle: string;
+ showMobileNav?: boolean;
+}) {
+ const mobileNavGroups: MobileNavGroup[] = [];
+ if (showMobileNav && navItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Chapters",
+ items: navItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/journals/${docSlug}/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ return (
+
+
+ {showMobileNav && (
+
+ )}
+ {prev ? (
+
+
+
+ {prev.label}
+
+
Previous
+
+ ) : (
+
+ )}
+
+ {next ? (
+
+
+ {next.label}
+
+
Next
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/journals/[slug]/page.tsx b/app/journals/[slug]/page.tsx
new file mode 100644
index 00000000..3d224761
--- /dev/null
+++ b/app/journals/[slug]/page.tsx
@@ -0,0 +1,138 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getJournalMeta,
+ slugToBookCode,
+ type JournalChapterEntry,
+} from "@/lib/journals";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent } from "@/components/ui/card";
+
+export default async function JournalIndexPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+ const bookCode = slugToBookCode(slug);
+ const meta = await getJournalMeta(bookCode);
+ if (!meta) notFound();
+
+ return (
+
+
+
+ ← Journals
+
+
+ {meta.title}
+
+ {meta.author}
+
+
+
+ {meta.description && (
+ {meta.description}
+ )}
+
+ {meta.chapters.map((ch: JournalChapterEntry, idx: number) => {
+ // Top-level chapter with a slug (Preface, Introduction, etc.)
+ if ("slug" in ch && !("subChapters" in ch)) {
+ return (
+
+
+
+
+ {ch.title}
+
+
+
+
+ );
+ }
+
+ // Group with sub-chapters (may be 2 or 3 levels deep)
+ if ("subChapters" in ch) {
+ const group = ch as {
+ title: string;
+ subChapters: {
+ slug?: string;
+ title: string;
+ subChapters?: { slug: string; title: string }[];
+ }[];
+ };
+ return (
+
+
+ {group.title}
+
+
+ {group.subChapters.map((sc, scIdx) => {
+ // Sub-chapter with its own nested sub-chapters (e.g., month → days)
+ if ("subChapters" in sc && sc.subChapters) {
+ return (
+
+
+ {sc.title}
+
+
+
+
+ {sc.subChapters.map((day) => (
+
+
+
+ {day.title}
+
+
+
+ ))}
+
+
+
+
+ );
+ }
+
+ // Direct sub-chapter with a slug (e.g., Summary, Notice)
+ if ("slug" in sc && sc.slug) {
+ return (
+
+
+
+
+ {sc.title}
+
+
+
+
+ );
+ }
+
+ return null;
+ })}
+
+
+ );
+ }
+
+ return null;
+ })}
+
+
+ );
+}
diff --git a/app/journals/page.tsx b/app/journals/page.tsx
new file mode 100644
index 00000000..cf00a129
--- /dev/null
+++ b/app/journals/page.tsx
@@ -0,0 +1,52 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { getAllJournals, bookCodeToSlug } from "@/lib/journals";
+
+export default async function JournalsPage() {
+ const journals = await getAllJournals();
+
+ return (
+
+
+
+ ← Home
+
+ Journals
+
+ Personal diaries and journals of Śrīla Prabhupāda
+
+
+
+
+
+ {journals.map((journal) => (
+
+
+
+ {journal.title}
+ {journal.description && (
+ {journal.description}
+ )}
+
+
+
+ {journal.year && {journal.year} }
+ {journal.totalChapters > 0 && (
+
+ {journal.totalChapters} {journal.totalChapters === 1 ? "chapter" : "chapters"}
+
+ )}
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/other-documents/[slug]/[chapter]/page.tsx b/app/other-documents/[slug]/[chapter]/page.tsx
new file mode 100644
index 00000000..35d11ec1
--- /dev/null
+++ b/app/other-documents/[slug]/[chapter]/page.tsx
@@ -0,0 +1,229 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import {
+ getOtherDocumentContent,
+ getOtherDocumentMeta,
+ getOtherDocumentSidebarItems,
+ slugToBookCode,
+ type OtherDocumentSidebarItem,
+} from "@/lib/other-documents";
+import { PageHeader } from "@/components/page-header";
+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";
+
+// ---------------------------------------------------------------------------
+// Page
+// ---------------------------------------------------------------------------
+
+export default async function OtherDocumentChapterPage({
+ params,
+}: {
+ params: Promise<{ slug: string; chapter: string }>;
+}) {
+ const { slug, chapter: chapterSlug } = await params;
+ const bookCode = slugToBookCode(slug);
+
+ const [meta, content, sidebarItems] = await Promise.all([
+ getOtherDocumentMeta(bookCode),
+ getOtherDocumentContent(bookCode, chapterSlug),
+ getOtherDocumentSidebarItems(bookCode),
+ ]);
+
+ if (!meta || !content) notFound();
+
+ const currentIdx = sidebarItems.findIndex(
+ (item) => item.slug === chapterSlug,
+ );
+ const prev = currentIdx > 0 ? sidebarItems[currentIdx - 1] : null;
+ const next =
+ currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
+
+ return (
+
+
+
+ ← {meta.title}
+
+
+ Chapter {content.chapter}:
+ {content.title}
+
+
+
+
+ {/* Left sidebar */}
+
+
+ {/* Main content */}
+
+ {/* Top navigation */}
+
+
+ {/* Content */}
+
+
+
+ {content.paragraphs.map((paragraph, i) => (
+
+ ))}
+
+
+
+
+ {/* Bottom navigation */}
+
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Sidebar
+// ---------------------------------------------------------------------------
+
+function ChapterSidebar({
+ items,
+ currentSlug,
+ docSlug,
+}: {
+ items: OtherDocumentSidebarItem[];
+ currentSlug: string;
+ docSlug: string;
+}) {
+ return (
+
+
+ Chapters
+
+
+ {items.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Prev / Next navigation
+// ---------------------------------------------------------------------------
+
+function ChapterNav({
+ prev,
+ next,
+ sidebarItems,
+ currentSlug,
+ docSlug,
+ docTitle,
+}: {
+ prev: OtherDocumentSidebarItem | null;
+ next: OtherDocumentSidebarItem | null;
+ sidebarItems: OtherDocumentSidebarItem[];
+ currentSlug: string;
+ docSlug: string;
+ docTitle: string;
+}) {
+ const mobileNavGroups: MobileNavGroup[] = [];
+
+ if (sidebarItems.length > 0) {
+ mobileNavGroups.push({
+ title: "Chapters",
+ items: sidebarItems.map((i) => ({
+ slug: i.slug,
+ label: i.label,
+ href: `/other-documents/${docSlug}/${i.slug}`,
+ type: i.type,
+ })),
+ });
+ }
+
+ return (
+
+
+
+ {prev ? (
+
+
+
+ Ch. {prev.chapter}
+
+
Previous
+
+ ) : (
+
+ )}
+
+
+ {next ? (
+
+
+ Ch. {next.chapter}
+
+
Next
+
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/other-documents/[slug]/page.tsx b/app/other-documents/[slug]/page.tsx
new file mode 100644
index 00000000..64e9c437
--- /dev/null
+++ b/app/other-documents/[slug]/page.tsx
@@ -0,0 +1,75 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { getOtherDocumentMeta, slugToBookCode } from "@/lib/other-documents";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent } from "@/components/ui/card";
+
+export default async function OtherDocumentIndexPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+ const bookCode = slugToBookCode(slug);
+
+ const meta = await getOtherDocumentMeta(bookCode);
+ if (!meta) notFound();
+
+ return (
+
+
+
+ ← Other Documents
+
+
+ {meta.title}
+
+ {meta.author}
+
+
+
+ {/* Description */}
+ {meta.description && (
+ {meta.description}
+ )}
+
+ {/* Chapters */}
+ {meta.chapters.length > 0 && (
+
+
+ Chapters
+
+
+
+
+ {meta.chapters.map((ch) => (
+
+
+
+
+ Chapter {ch.number}
+
+
+ {ch.title}
+
+
+
+
+ ))}
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/app/other-documents/other-documents-grid.tsx b/app/other-documents/other-documents-grid.tsx
new file mode 100644
index 00000000..b2902e3d
--- /dev/null
+++ b/app/other-documents/other-documents-grid.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import { useState, useMemo } from "react";
+import Link from "next/link";
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardContent,
+} from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Input } from "@/components/ui/input";
+import { cn } from "@/lib/utils";
+import { Search } from "lucide-react";
+
+export type OtherDocumentInfo = {
+ slug: string;
+ title: string;
+ description: string;
+ year: number | null;
+ totalChapters: number;
+ href: string;
+};
+
+/**
+ * Strip diacritics / accents so that "Krsna" matches "Kṛṣṇa",
+ * "Gita" matches "Gītā", "Srimad" matches "Śrīmad", etc.
+ */
+function normalize(text: string): string {
+ return (
+ text
+ .toLowerCase()
+ .replace(/ṛ/g, "r")
+ .replace(/ṝ/g, "r")
+ .replace(/ṣ/g, "s")
+ .replace(/ś/g, "s")
+ .replace(/ṭ/g, "t")
+ .replace(/ḍ/g, "d")
+ .replace(/ṇ/g, "n")
+ .replace(/ñ/g, "n")
+ .replace(/ṅ/g, "n")
+ .replace(/ḥ/g, "h")
+ .replace(/ṁ/g, "m")
+ .replace(/ṃ/g, "m")
+ .replace(/ḷ/g, "l")
+ .normalize("NFD")
+ .replace(/[\u0300-\u036f]/g, "")
+ );
+}
+
+export function OtherDocumentsGrid({
+ items,
+}: {
+ items: OtherDocumentInfo[];
+}) {
+ const [query, setQuery] = useState("");
+
+ const normalizedItems = useMemo(
+ () =>
+ items.map((item) => ({
+ item,
+ searchText: normalize(
+ `${item.title} ${item.description} ${item.slug}`,
+ ),
+ })),
+ [items],
+ );
+
+ const filtered = useMemo(() => {
+ if (!query.trim()) return items;
+ const q = normalize(query.trim());
+ return normalizedItems
+ .filter((entry) => entry.searchText.includes(q))
+ .map((entry) => entry.item);
+ }, [items, normalizedItems, query]);
+
+ return (
+
+ {/* Search input */}
+
+
+ setQuery(e.target.value)}
+ className="pl-9"
+ />
+
+
+ {/* Results count when searching */}
+ {query.trim() && (
+
+ {filtered.length === 0
+ ? "No documents found"
+ : `${filtered.length} document${filtered.length === 1 ? "" : "s"} found`}
+
+ )}
+
+ {/* Grid */}
+
+ {filtered.map((item) => (
+
+
+
+
+ {item.title}
+
+ {item.description && (
+
+ {item.description}
+
+ )}
+
+
+
+ {item.year && (
+
+ {item.year}
+
+ )}
+ {item.totalChapters > 0 && (
+
+ {item.totalChapters}{" "}
+ {item.totalChapters === 1 ? "chapter" : "chapters"}
+
+ )}
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/other-documents/page.tsx b/app/other-documents/page.tsx
new file mode 100644
index 00000000..40dfe103
--- /dev/null
+++ b/app/other-documents/page.tsx
@@ -0,0 +1,42 @@
+export const dynamic = "force-dynamic";
+
+import Link from "next/link";
+import { PageHeader } from "@/components/page-header";
+import { getAllOtherDocuments, bookCodeToSlug } from "@/lib/other-documents";
+import { OtherDocumentsGrid } from "./other-documents-grid";
+
+export default async function OtherDocumentsPage() {
+ const documents = await getAllOtherDocuments();
+
+ const items = documents.map((doc) => ({
+ slug: bookCodeToSlug(doc.book),
+ title: doc.title,
+ description: doc.description ?? "",
+ year: doc.year,
+ totalChapters: doc.totalChapters,
+ href: `/other-documents/${bookCodeToSlug(doc.book)}`,
+ }));
+
+ return (
+
+
+
+ ← Home
+
+
+ Other Documents
+
+
+ Miscellaneous documents, analyses, and writings by Śrīla Prabhupāda
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
index 2a6d7fc9..b70c7b22 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -9,7 +9,14 @@ import {
import { Badge } from "@/components/ui/badge";
import { PageHeader } from "@/components/page-header";
import { Logo } from "@/components/logo";
-import { BookOpen, FileText, BookA, PenLine } from "lucide-react";
+import {
+ BookOpen,
+ FileText,
+ BookA,
+ PenLine,
+ FileStack,
+ NotebookPen,
+} from "lucide-react";
const allowRtf = process.env.ALLOW_RTF === "true";
@@ -76,6 +83,35 @@ export default function Home() {
+ {/* Journals */}
+
+
+
+
+
+
+
+
+
+ Journals
+
+
+ Personal diaries & journals
+
+
+
+
+
+
+ Śrīla Prabhupāda's personal diaries — including the
+ historic Jaladuta Diary from his 1965 voyage to America and
+ The Beginning, his 1966 New York journal documenting the
+ founding of the Hare Kṛṣṇa movement.
+
+
+
+
+
{/* Glossary */}
@@ -104,6 +140,35 @@ export default function Home() {
+ {/* Other Documents */}
+
+
+
+
+
+
+
+
+
+ Other Documents
+
+
+ Miscellaneous analyses & writings
+
+
+
+
+
+
+ Miscellaneous documents, analyses, and writings by Śrīla
+ Prabhupāda that don't fit neatly into the books or essays
+ collections — including structured analyses with verse
+ references.
+
+
+
+
+
{/* RTF Document Viewer — only visible when ALLOW_RTF=true */}
{allowRtf && (
diff --git a/components/mobile-nav.tsx b/components/mobile-nav.tsx
index 60c318d7..9a027fb6 100644
--- a/components/mobile-nav.tsx
+++ b/components/mobile-nav.tsx
@@ -87,11 +87,11 @@ export function MobileNav({
)}
- {group.items.map((item) => {
+ {group.items.map((item, ii) => {
if (item.isParent) {
return (
{item.label}
@@ -103,7 +103,7 @@ export function MobileNav({
const isChild = item.parentChapter != null && !item.isParent;
return (
-
+
][\s\S]*?<\/script>/gi, "");
+ clean = clean.replace(/