Files

49 KiB
Raw Permalink Blame History

Prabhupāda Says — Project Context

What this project is

A Next.js app presenting the books, letters, essays, lectures, and teachings of His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda in a modern, accessible, and searchable format with proper Unicode Sanskrit (IAST + Devanagari).

Content is sourced from the pre-1980s Vedabase, extracted and indexed into MongoDB, and served via a Next.js frontend. Extraction scripts and raw data live separately in the extractor/ directory.


Tech Stack

  • Next.js 16 with Turbopack, React 19, TypeScript
  • Tailwind CSS v4 with @tailwindcss/postcss
  • shadcn/ui (base-nova style, lucide icons) — components: Button, Card, Badge, Tooltip, Input, Drawer
  • next-themes for dark/light/system mode
  • vaul for mobile drawer navigation
  • Go BFF API — all data fetched via lib/bff.ts HTTP client calling the Go BFF server (no direct database access)
  • framer-motion for audio player animations (spring enter/exit, minimize/expand transitions, audio wave bars)
  • Typesense (via BFF) — typo-tolerant full-text search across all content
  • Python 3 with BeautifulSoup4 + indic-transliteration for HTML extraction scripts

Project Structure

The project is split into two directories:

  • knowledgebase/ — Production Next.js app (~3.5MB excluding node_modules)
  • extractor/ — Extraction scripts, seed scripts, raw data, legacy fonts (~750MB)
knowledgebase/
├── app/
│   ├── page.tsx                → Landing page (Books, Essays, Magazines, Letters, Transcriptions, etc.)
│   ├── layout.tsx              → ThemeProvider + TooltipProvider + TextSelectionToolbar + TextHighlighter
│   ├── globals.css             → Vintage color palette + reading settings CSS
│   ├── about/page.tsx          → About page (mission, data source, roadmap)
│   ├── books/                  → 37 book routes (verse-based + prose)
│   │   ├── page.tsx            → Books index (searchable grid, diacritic-aware search)
│   │   ├── chapter-viewer.tsx  → Shared client component for verse-based books
│   │   ├── bg/, sb/, cc/, sb1962/, iso/, noi/, bs/, mm/, nbs/  → Verse-based books
│   │   ├── kb/, nod/, tlc/, ej/, ek/, mg/, bbd/, owk/, rv/    → Prose books
│   │   ├── poy/, pqpa/, ttp/, ochkm/, hkh/, lcl/, lob/        → More prose books
│   │   ├── ssr/, ds/           → Hierarchical prose books (sub-chapters)
│   │   └── sva/, gg/, mog/, cbhc/, bk/, pk/, va/              → Other books
│   ├── magazines/              → Magazine browsing (BTG 1944-1960, BTG 1966-1973)
│   ├── letters/
│   │   ├── page.tsx            → Letters index with 3-way nav (year/recipient/location)
│   │   ├── filter-year-browser.tsx → Shared component: sidebar years + month-grouped content
│   │   ├── [slug]/page.tsx     → Individual letter reader
│   │   ├── year/[year]/
│   │   │   ├── page.tsx        → Letters by year
│   │   │   └── year-month-browser.tsx → Sidebar months + letter list
│   │   ├── recipient/[name]/page.tsx → Letters by recipient (year sidebar + month groups)
│   │   └── location/[name]/page.tsx  → Letters by location (year sidebar + month groups)
│   ├── essays/                 → 23 essays across 6 groups
│   ├── journals/               → Jaladuta Diary, The Beginning
│   ├── other-documents/        → 12 miscellaneous documents
│   ├── glossary/
│   │   ├── page.tsx            → Redirects to /glossary/a
│   │   ├── glossary-client.tsx → Client component (sidebar terms, single entry view, prev/next nav)
│   │   └── [letter]/page.tsx   → Dynamic route per letter (A-Z)
│   ├── disciples/              → 4,747 initiated disciples directory
│   │   ├── page.tsx            → Disciples browser (letter nav, suffix filter, search, sort, pagination)
│   │   └── disciples-browser.tsx → Client component
│   ├── transcriptions/         → 3,787 transcriptions (lectures, conversations, philosophy, dictations)
│   │   ├── page.tsx            → Browse by year / place / type (3 views)
│   │   ├── year/[year]/page.tsx → Year listing, grouped by month
│   │   ├── place/[place]/page.tsx → Place listing
│   │   ├── type/[type]/page.tsx → Type listing (lecture/conversation), year sidebar
│   │   └── [slug]/page.tsx     → Individual transcription reading page
│   ├── search/                 → Search page (server + client components)
│   │   ├── page.tsx            → Server component (URL-driven state)
│   │   └── search-content.tsx  → Client component (input, filters, results)
│   └── api/
│       ├── og/route.tsx        → Dynamic OG image generator (Noto Sans, Prabhupāda image)
│       ├── books/              → Book/verse API routes
│       ├── search/route.ts     → BFF search proxy (Typesense via /api/v1/search)
│       └── glossary/route.ts   → Glossary API
├── components/
│   ├── ui/                     → shadcn components (button, card, badge, tooltip, input, drawer)
│   ├── selection-toolbar/      → Text selection toolbar sub-components
│   │   ├── shared.tsx          → KautilyaIcon, types, hooks (useMediaQuery, useCopyFeedback)
│   │   ├── floating-toolbar.tsx → Desktop floating pill / mobile bottom bar
│   │   ├── share-modal.tsx     → Share modal (desktop) / drawer (mobile)
│   │   ├── collections-drawer.tsx → Save to collection drawer
│   │   └── more-popover.tsx    → More options popover (desktop) / drawer (mobile)
│   ├── text-selection-toolbar.tsx → Main orchestrator (event listeners, state management)
│   ├── text-highlighter.tsx    → Processes incoming ?hl= URLs (scroll + highlight)
│   ├── page-header.tsx         → Shared header with ReadingSettings (max-w-5xl)
│   ├── reading-settings.tsx    → Theme/font/size/justify accessibility panel
│   ├── mobile-nav.tsx          → Left-slide drawer for mobile sidebar navigation
│   ├── header-search.tsx       → Header search button with framer-motion expand animation
│   ├── site-footer.tsx         → Global footer (About + Home links)
│   ├── audio-player/           → Site-wide floating audio player
│   │   ├── audio-context.tsx   → React context + provider, manages <audio> element
│   │   ├── floating-player.tsx → Floating player UI (desktop bar / mobile pill)
│   │   ├── audio-wave.tsx      → Reusable animated audio wave bars
│   │   └── index.ts            → Barrel exports
│   ├── logo.tsx                → Theme-aware logo component
│   └── theme-provider.tsx      → next-themes wrapper
├── lib/
│   ├── bff.ts                  → BFF API client (typed fetch wrapper: bff<T>(), bffList<T>(), bffOrThrow<T>())
│   ├── api.ts                  → Core data access (books, verses, prose chapters, glossary) — calls BFF API
│   ├── books.ts                → Generic book data layer (replaces 29 individual book files)
│   ├── constants.ts            → Shared constants (BOOK_NAMES, CC_LILAS)
│   ├── og.ts                   → OG image metadata builders (ogForVerse, ogForLetter, etc.)
│   ├── text-highlight.ts       → Text fragment encode/decode, highlight matching
│   ├── source-reference.ts     → Source reference from URL (for copy attribution)
│   ├── disciples.ts            → Disciples data access — calls BFF API
│   ├── letters.ts              → Letters data access (by year, recipient, location) — calls BFF API
│   ├── transcriptions.ts       → Transcriptions data access — calls BFF API
│   ├── utils.ts                → cn() helper
│   ├── [book].ts               → Per-book data access layers (nod, tlc, iso, noi, ej, ek, etc.) — calls BFF API
│   ├── other-documents.ts      → Other Documents data access — calls BFF API
│   ├── journals.ts             → Journals data access — calls BFF API
│   └── magazines.ts            → Magazines data access — calls BFF API
└── public/
    ├── fonts/Noto_Sans/static/ → NotoSans-Regular.ttf + NotoSans-Bold.ttf (OG images)
    ├── images/                 → OG card image (Prabhupāda)
    ├── logo_dark.png           → Logo for light theme
    ├── logo_light.png          → Logo for dark theme
    └── robots.txt

extractor/                      → Extraction & seeding tools (not deployed)
├── scripts/                    → Python extractors + TypeScript seed scripts
├── jsons/                      → Extracted JSON data (books, letters, essays, etc.)
├── html/                       → RTF→HTML converted files
├── rtfs/                       → Original RTF source files
├── fonts/                      → Legacy custom fonts (ScaGoudy, Indevr, Balaram, etc.)
├── app-rtfs/                   → RTF viewer pages (archived)
└── .venv/                      → Python virtual environment

Data Architecture

The knowledgebase never connects to MongoDB directly. All data is served by the Go BFF API server.

BFF Server

  • URL: Configured via BFF_URL environment variable (default: http://localhost:8080)
  • The BFF reads from MongoDB — the knowledgebase frontend has no database driver or connection string
  • All API endpoints live under /api/v1/... on the BFF server

BFF Client (lib/bff.ts)

The typed fetch wrapper provides three helpers:

  • bff<T>(path, options?) — Fetch JSON from the BFF. Returns null on 404, throws BffError on other failures.
  • bffList<T>(path, options?) — Fetch a list. Returns [] on 404 (instead of null).
  • bffOrThrow<T>(path, options?) — Fetch JSON, throwing on 404. Use when a missing resource should be an error.

Options include params (query parameters), revalidate (Next.js ISR seconds, default 60), and tags (cache tags).

Response Envelope

BFF responses use a { success: true, data: ... } envelope. The bff() client auto-unwraps this — callers receive the inner data value directly.

Case Mapping

The BFF uses snake_case JSON field names (Go convention). Each frontend data layer file (lib/letters.ts, lib/transcriptions.ts, lib/books.ts, etc.) maps BFF responses to camelCase TypeScript types used by React components.

Data Layer Files

Individual lib/*.ts files are thin wrappers that:

  1. Call a BFF endpoint via bff() / bffList() / bffOrThrow()
  2. Map the snake_case response to camelCase frontend types
  3. Export typed functions consumed by Next.js server components

Key data layers and their BFF endpoint prefixes:

  • lib/api.ts → /api/v1/books/*, /api/v1/glossary/* — Books, verses, prose chapters, glossary
  • lib/books.ts → /api/v1/books/{code}/* — Generic book data layer (replaces 29 individual per-book files)
  • lib/letters.ts → /api/v1/letters/* — Letters (by year, recipient, location)
  • lib/transcriptions.ts → /api/v1/transcriptions/* — Transcriptions (by year, place, type)
  • lib/disciples.ts → /api/v1/disciples/* — Disciples directory
  • lib/other-documents.ts → /api/v1/other-documents/* — Other Documents
  • lib/journals.ts → /api/v1/journals/* — Journals
  • lib/magazines.ts → /api/v1/magazines/* — Magazines

Disciples Collection

Scraped from https://sp.krishna.com/disciples_a through disciples_y (21 letter pages; F, Q, W, X, Z have no pages).

  • Total: 4,747 disciples
  • 3,296 Dasa, 1,450 Devi Dasi, 1 Svāmī
  • 4,554 have initiation dates (month + year); 193 are missing dates
  • 4,703 have initiation places; 44 are missing places
  • Initiation dates are set to the 1st day of the month (exact day unknown from this source)
  • On the UI, exact dates should only be shown when isVerified is true
  • karmiName, avatar, about, and tags are empty by default — intended for manual curation

Data files: data/disciples_a_b_c_d_e.json, data/disciples_g_to_l.json, data/disciples_m_n_o_p.json, data/disciples_r_s_t.json, data/disciples_u_v_y.json

Seed script: npx tsx scripts/seed-disciples.ts


Books Extracted

Verse-based Books (stored in verses collection)

Book Code Verses Chapters Division
Bhagavad-gītā As It Is bg 653 18 —
Śrīmad-Bhāgavatam sb 8,258 213 10 cantos
Śrī Caitanya-caritāmṛta cc 11,348 62 3 līlās
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)

Book Code Chapters Source
Kṛṣṇa Book kb 89 (2 volumes) other.html
The Nectar of Devotion nod 51 (4 parts) other.html
Teachings of Lord Caitanya tlc 32 (2 parts) other.html (1968 ed., plain English)
Easy Journey to Other Planets ej 2 other.html
Elevation to Kṛṣṇa Consciousness ek 6 other.html
Kṛṣṇa Consciousness: The Matchless Gift mg 7 other.html
Beyond Birth and Death bbd 5 other.html
On the Way to Kṛṣṇa owk 5 other.html
Rāja-vidyā: The King of Knowledge rv 8 other.html
The Perfection of Yoga poy 11 other.html
Perfect Questions Perfect Answers pqpa 9 other.html
Transcendental Teachings of Prahlāda Mahārāja ttp 5 other.html
On Chanting the Hare Kṛṣṇa Mantra ochkm 4 other.html
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
Vaiśiṣṭyāṣṭakam va 8 others-before-1977.html

Essays (stored in essays collection)

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
Man Is the Architect of His Own Fortune man-is-the-architect-of-his-own-fortune pre-1977-essays —
What Is the Matter with the World? what-is-the-matter-with-the-world pre-1977-essays 1957
Religion Is Godly Life religion-is-godly-life pre-1977-essays —
Truth and Beauty truth-and-beauty pre-1977-essays 1958
Misuse of Public Fund in the Name of Religion misuse-of-public-fund-in-the-name-of-religion pre-1977-essays 1958
Perfection at Home perfection-at-home pre-1977-essays —
How Should One Cultivate Human Spirit how-should-one-cultivate-human-spirit pre-1977-essays 1961
Viswa Dharma viswa-dharma pre-1977-essays —
Glorification of Śrīla Bhaktisiddhānta Sarasvatī Ṭhākura glorification-of-srila-bhaktisiddhanta-sarasvati-thakura pre-1977-essays 1952
Who Is Crazy? (Back to Godhead #66) who-is-crazy-btg66 pre-1977-essays —
Manifestation of Dharma in Human Being manifestation-of-dharma-in-human-being pre-1977-essays 1972
Message of Krishna Consciousness message-of-krishna-consciousness pre-1977-essays 1972
A Wrong Civilization a-wrong-civilization pre-1977-essays 1936
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 other_document_meta + other_document_chapters collections)

Document Code Chapters
An Analysis of Bhagwat Geeta analysis-bg 3
Solution of Present Crisis by Bhagwat Geeta solution-crisis 4
Śrī Caitanya Mahāprabhu sri-caitanya 3
Conception of Geeta Nagari on the Principles of Gandhi geeta-nagari 1
The Real Peace Formula real-peace-formula 3
International Institution for God Consciousness intl-institution-god-consciousness 1
"Srimad Bhawatam" India's Message of Peace and Goodwill srimad-bhawatam-message 1
Viraha Aṣṭaka viraha-astaka 8
Vṛndāvana Bhajana vrndavana-bhajana 1
Prayer unto the Lotus Feet of Kṛṣṇa prayer-unto-lotus-feet-of-krsna 1
Märkine Bhägavata-dharma markine-bhagavata-dharma 1
Jaladuta Pamphlet jaladuta-pamphlet 1

Journals (stored in journal_meta + journal_chapters collections)

Journal Code Chapters Structure
The Jaladuta Diary, 1965 jaladuta-diary 85 Preface, Introduction, 83 day entries grouped by month (Apr–Nov 1965)
The Beginning — The 1966 New York Journal the-beginning 215 Introduction, 3 place-based chapters with day entries, Memoranda, Epilogue, 20 Excerpts

Magazines (stored in magazine_meta + magazine_issues collections)

Magazine Code Issues Articles Flag Source
Back to Godhead (1944–1960) btg 29 89 Pre 1978 btg-1960.html
Back to Godhead (1966–1973) btg-1978 30 1,035 Pre 1978 btg-1978.html

Letters (stored in letters collection)

Metric Value
Total letters 6,866
Unique recipients (individual) 2,243
Unique locations 164
Year range 1947–1977
Total paragraphs 52,261

Top recipients: Satsvarupa (182), Brahmananda (168), Hansadutta (117), Tamala Krsna (115), Gurudasa (103), Mukunda (105), Madhudvisa (90), Karandhara (90)

Top locations: Los Angeles (1,392), Bombay (1,086), Vrindaban (532), Mayapur (305), London (264), Calcutta (250), Honolulu (239)

Schema notes:

  • recipients is an array of individual names (e.g., ["Mukunda", "Janaki"])
  • recipientDisplay is the original combined string (e.g., "Mukunda, Janaki")
  • Searching for a recipient filters via the BFF endpoint (matches any letter where that person appears)
  • Multi-recipient groups are split by ,, ;, and, &
  • Trailing punctuation (:, ,, .) cleaned from names

Transcriptions (stored in transcriptions collection)

3,787 transcriptions of Śrīla Prabhupāda's lectures and conversations (1966–1977), plus 59 philosophy discussions and 12 SB dictations.

  • Lectures (2,179): Bhagavad-gītā, Śrīmad-Bhāgavatam, Caitanya-caritāmṛta, Nectar of Devotion, Īśopaniṣad, Brahma-saṁhitā, and general lectures
  • Conversations (1,537): Morning walks, room conversations, interviews, press conferences, initiations
  • Philosophy Discussions (59): Western philosophers discussed with Śyāmasundara and Hayagrīva
  • SB Dictations (12): Śrīmad-Bhāgavatam 3rd Canto translation/purport dictations
  • Audio: 99.1% have linked audio recordings from Cloudflare R2 (audioUrl field)
  • Empty content: 30 transcriptions (Hindi/Bengali lectures) have no English text but may have audio

Data layer: lib/transcriptions.ts (calls BFF API, NOT local JSON files or direct MongoDB)

Other

Content Collection
Glossary (2,749 entries) glossary
Transcriptions (3,787 entries) transcriptions collection

Font Encoding System

ScaGoudy → IAST mapping (used in BG, SB, CC, ISO, NOI, NoD, and most 1969+ books)

ä→ā  Ä→Ā  å→ṛ  Å→Ṛ  ñ→ṣ  Ñ→Ṣ  ö→ṭ  Ö→Ṭ  ë→ṇ  Ë→Ṇ  ò→ḍ  Ò→Ḍ
ç→ś  Ç→Ś  ì→ṅ  ï→ñ  ü→ū  Ü→Ū  é→ī  É→Ī  à→ṁ  ù→ḥ  è→ṝ  û→ṁl  ÿ→ḷ

Bengali fonts (Inbenr/Inbeni in CC)

Custom-encoded fonts where ASCII codepoints render as Bengali script. Fixed by fix-bengali-encoding.py.

RM Devanagari font (used in ISO, NOI)

Custom-encoded Devanagari font (p51 CSS class). The text appears as garbled ASCII without the font. Skipped during extraction — Unicode Devanagari is generated from IAST transliteration instead.

Devanagari generation

IAST transliteration → Devanagari via indic-transliteration library. Key fixes:

  • join_virama_words() — joins virāma + consonant AND virāma + vowel
  • normalize_devanagari_anusvara() — ङ् → ं before velars, ञ् → ं before palatals

Books without ScaGoudy encoding

  • 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

Book Types and URL Patterns

Verse-based books (use ChapterViewer client component)

Route Pattern Books
/books/bg/[chapter]/[verse] BG
/books/sb/[canto]/[chapter]/[verse] SB
/books/cc/[lila]/[chapter]/[verse] CC
/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, LCL, LOB, TLK, TQK, GG, MOG, CBHC, BK, PK

Hierarchical prose books (sub-chapters as separate pages)

Route Pattern Books
/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 Searchable essay grid (23 essays across 6 groups)
/essays/[slug] Individual essay reader

Other Documents

Route Content
/other-documents Searchable other documents grid (12 documents)
/other-documents/[slug] Document index (chapter listing)
/other-documents/[slug]/[chapter] Individual chapter reader

Journals

Route Content
/journals Journals index (grid)
/journals/[slug] Journal chapter listing (hierarchical: place → month → day)
/journals/[slug]/[chapter] Individual journal entry reader (slug-based, e.g., apr-3, sep-17, excerpt-...)

Magazines

Route Content
/magazines Magazines index (grouped by flag: "Pre 1978 Editions")
/magazines/[slug] Magazine issue listing (grouped by volume)
/magazines/[slug]/[issue] Individual issue reader (slug-based, e.g., v1-p1, no-17)

Letters

Route Content
/letters Letters index with 3-way navigation tabs
/letters?view=year Year grid (1947–1977) with search
/letters?view=recipient Recipient list (2,243 individuals) with search + pagination
/letters?view=location Location grid (164 locations) with search + pagination
/letters/year/1968 All letters from a specific year
/letters/recipient/Mukunda All letters to/including a specific recipient
/letters/location/Los%20Angeles All letters from a specific location
/letters/[slug] Individual letter reader with prev/next navigation

Transcriptions

/transcriptions                         → Browse by year / place / type
/transcriptions?view=year               → Year grid view
/transcriptions?view=place              → Place grid view  
/transcriptions?view=type               → Lecture vs Conversation cards
/transcriptions/year/[year]             → Year listing, grouped by month
/transcriptions/place/[place]           → Place listing
/transcriptions/type/[type]             → Type listing (lecture/conversation), year sidebar
/transcriptions/[slug]                  → Individual transcription reading page

Each transcription page shows:

  • Title, type badge (Lecture/Conversation), date, place, reference, duration
  • Participants list
  • Play Audio button (links to Cloudflare R2 mp3)
  • Content blocks: paragraphs with speaker labels, verses (centered italic), quotes (border-left), notes
  • Prev/next navigation within same year
  • ?hl= text highlight sharing support with OG image

Other routes

Route Content
/ Landing page (responsive 3-column grid)
/books Searchable book grid
/glossary Searchable glossary (2,749 entries)
/journals Journals (2 journals, 300 chapters)
/magazines Magazines (2 magazines, 59 issues, Pre 1978)
/letters Letters (6,866 letters, 3-way navigation)
/rtfs RTF viewer (gated by ALLOW_RTF=true)
/api/verify-access Password protection endpoint

Volume Convention

Books without volumes use volume: 0 as a sentinel value in the prose_chapters collection. This satisfies the Prisma schema's @@unique([book, volume, slug]) constraint without requiring schema changes.

  • KB uses volume: 1 and volume: 2
  • All other prose books use volume: 0

Hierarchical Books (SSR, DS)

SSR and DS have parent chapters with sub-chapters stored as individual prose_chapters docs:

  • Sub-chapter slugs: "1-1", "1-2", "2-1", etc.
  • Parent chapter info stored in intro.json metadata:
    {
      "chapters": [
        {
          "number": 1,
          "title": "Learning the Science of the Soul",
          "subChapters": [
            { "slug": "1-1", "title": "Seeing the Aim of Human Life" },
            { "slug": "1-2", "title": "Your Original Consciousness Is Kṛṣṇa Consciousness" }
          ]
        }
      ]
    }
    
  • Sidebar UI shows parent headings (non-clickable) with sub-chapters indented below
  • Prev/next navigation skips parent headings

UI Features

Reading Settings (accessibility)

  • Theme: Light / Dark / System toggle
  • Palette: 3 light + 3 dark colour palettes, each selectable from a swatch grid in the settings panel
    • Light: Aged Parchment (warm cream/saffron — default), Tulasī Garden (sage green/emerald), Yamunā Mist (sky-blue/cerulean)
    • Dark: Ancient Manuscript (warm brown/saffron — default), Nīlamaṇi (deep navy/sky-blue — Kṛṣṇa's "blue jewel"), Vraja Twilight (deep wine/rose-red)
    • Swatches in the settings panel show only the palettes for the currently resolved theme (uses resolvedTheme from next-themes, so System mode resolves correctly)
    • Each palette defines a full set of CSS variables (background, foreground, primary, card, muted, accent, border, etc.) so the entire UI adapts cohesively
    • Palette CSS lives in app/globals.css under :root:not(.dark)[data-light-palette="..."] and .dark[data-dark-palette="..."] selectors. The :not(.dark) scoping prevents light-palette attribute rules from overriding .dark (an attribute selector has higher specificity than a plain class selector)
  • Serif Font: Toggle to switch reading content to Georgia/serif
  • Font Size: Slider (XS → XXL) using CSS zoom on main and aside
  • Justify Text: Toggle for justified text alignment on reading content
  • Settings persist in localStorage under spsays_reading_prefs (now includes lightPalette and darkPalette fields)
  • Applied via CSS classes (reading-serif, reading-fs-*, reading-justified) AND data attributes (data-light-palette, data-dark-palette) on <html> element

Mobile Navigation

  • Left-slide drawer (vaul) triggered by a menu button next to the Previous button
  • Only visible below lg breakpoint
  • Shows the full sidebar navigation grouped by section
  • Implemented in all 24+ reader pages via shared MobileNav component

Page Header (components/page-header.tsx)

  • Shared client component used on every page — wraps the title/breadcrumb area and the right-side actions (search button + reading settings)
  • Mobile layout uses CSS float-right on the actions block so the title text genuinely wraps around them on the first line and uses the full container width on every subsequent line. (CSS padding-right was previously used but applies the same gap to every line, leaving an empty column down the right edge.)
  • When the header search is expanded on mobile, the actions block drops the float, the logo/title area is hidden via max-md:hidden, and the search bar fills the full header width edge-to-edge (no ml-2 margin in the expanded state)
  • Desktop layout is unchanged — standard md:flex md:items-start md:justify-between md:gap-4 with the actions block on the right (md:order-1 md:shrink-0)
  • Reads a sessionStorage flag (spsays_from_search) on first render via a lazy useState initialiser; if set, renders a “← Back to search results” button that calls router.back() and hides the page's own first-child <Link> breadcrumb via [&>a:first-child]:hidden so there's never a double-arrow stack
  • The lazy initialiser also clears the flag on read (one-shot), so navigating from the result page to a subsequent page (e.g. next verse) doesn't show the search return button again

Search (/search page) — Typo-tolerant, diacritic-insensitive full-text search across all content.

  • Powered by Typesense via the BFF (/api/v1/search proxied through /api/search)
  • 9 indexed content types combined into 8 user-facing filters: Books (verses + prose), Letters, Transcriptions, Glossary, Essays, Journals, Magazines, Other Documents
  • Books filter combines verses and prose into one tab (sends type=verses,prose to the BFF)
  • Multi-select filters — clicking multiple type tabs combines them (e.g., Books + Letters); clicking "All" clears all filters
  • URL state — q, type, and page are synced to the URL (e.g. /search?q=foo&type=books,letters&page=2), so any search is shareable and bookmarkable
  • Pagination — appears both above the results (compact: prev/next + page count) and below them
  • Result cards — the entire card is a clickable Link that navigates to the source page
  • Header search — HeaderSearchButton on every page header expands into a search input on click; the / keyboard shortcut opens the search input from any page
  • 300ms debounce on input
  • Highlighted snippets — matched text wrapped in <mark> tags
  • Typo tolerance — "Krishna", "Krsna", "Krishn" all find Kṛṣṇa results
  • Diacritic insensitivity — _ascii shadow fields indexed alongside originals (e.g., "Yashodanandana" matches "Yaśodanandana")
  • Prefix matching — partial words like "sarv dharman parit" match sarva-dharmān parityajya
  • Hyphenated terms — every collection (not just verses) has token_separators: ["-"], so Dīkṣā-guru indexes as two tokens (dīkṣā + guru) and a query for "diksa guru" matches it cleanly
  • Stopwords — a server-side Typesense stopwords set (common) strips conversational tokens like letter, to, of, essay, magazine, etc. from queries so they don't drown out specific intent. e.g. "letter to tusta krsna" effectively searches for "tusta krsna" and surfaces actual letters to that recipient instead of any letter whose body contains "letter" + "to"
  • Exact > prefix > typo ranking — prioritize_exact_match=true + prioritize_token_position=true so "karma" outranks "karmic", and exact recipient matches outrank typo matches
  • Glossary results — glossary pages read ?term= from the URL so a search result lands on the correct term (with diacritic-insensitive matching)
  • Defensive null handling — the frontend always treats a null results payload as an empty array
  • Files: app/search/page.tsx, app/search/search-content.tsx, app/api/search/route.ts, components/header-search.tsx

Header search behaviour. When the user submits a search from the inline header input, HeaderSearchButton automatically appends a type query param matching the section they're currently in (via usePathname()):

Current path Search URL gets Active tab
/books/** type=books Books
/letters/** type=letters Letters
/transcriptions/** type=transcriptions Transcriptions
/essays/** type=essays Essays
/glossary/** type=glossary Glossary
/journals/** type=journals Journals
/magazines/** type=magazines Magazines
/other-documents/** type=other_documents Other Documents
/, /search, /about no type param All

Header search expand UX. The collapsed icon expands into a 400px-wide bar on desktop (capped via md:max-w-100, animated to width: 100% of its container by framer-motion) and a full-width bar on mobile. The bar has a “Close” X on the left, the text input in the middle, and a primary-coloured “Search ⏎” submit button on the right — the CornerDownLeft icon hints that Enter also submits. Pressing Esc collapses. Spring animation on enter, instant collapse (no exit animation, no AnimatePresence — the previous use of AnimatePresence mode="popLayout" caused a backwards-shrink flash because popLayout pulled the exiting element out of flow and ran a layout-level FLIP).

Back to search results. When a user clicks a result card, ResultCard's onClick writes a one-shot spsays_from_search flag to sessionStorage. On the destination page, PageHeader reads (and consumes) this flag in a lazy useState initialiser; if present, it replaces the page's own breadcrumb link with a “← Back to search results” button that calls router.back(). Client-side navigation preserves AudioPlayerProvider so audio playback continues uninterrupted.

Search-result highlight. Each result card builds its destination URL through buildSearchResultUrl(result.url, result.snippet) from lib/text-highlight.ts. This extracts the text inside the snippet's <mark> tags (from first mark to last mark, HTML stripped), encodes it via the existing encodeTextFragment() helper, and appends it as ?hl=.... The destination page's existing TextHighlighter then finds and highlights the matched phrase — reusing the exact same ?hl= infrastructure as share links, with no changes to TextHighlighter itself.

Search relevance. Search is tuned to find verses where the verse text itself contains the query, not commentaries that cite it. Verse purports are intentionally excluded from the default search so results stay focused on primary sources. In practice this means searching for sarva dharman parityajya returns BG 18.66 first (the actual verse) rather than the many commentaries that quote it.

Per-Page Filters

  • Books page: diacritic-aware search (typing "Krsna" matches "Kṛṣṇa")
  • Glossary page: full-text search across terms and definitions
  • Uses Unicode NFD normalization + manual IAST character stripping

Color Palettes

Three themes per mode, all named after concepts from the Vedic tradition. Each defines a complete set of CSS variables (background, foreground, primary, card, muted, accent, border, ring, sidebar variants, charts) for cohesive styling site-wide.

Light palettes

  • Aged Parchment (default) — warm cream backgrounds, deep sepia text, saffron/ochre primary
  • Tulasī Garden — sage-green backgrounds, deep forest text, emerald primary; inspired by the sacred Tulasī plant
  • Yamunā Mist — soft blue backgrounds, deep teal text, cerulean primary; inspired by the sacred Yamunā river

Dark palettes

  • Ancient Manuscript (default) — deep warm brown backgrounds, parchment text, golden saffron primary
  • Nīlamaṇi — deep navy backgrounds, soft moonlight text, sky-blue primary; “blue jewel,” one of Kṛṣṇa's epithets
  • Vraja Twilight — deep wine backgrounds, warm cream text, rose-red primary; the crimson dusk over Vraja

Palettes are switched independently of theme: the user can pick their preferred light palette AND their preferred dark palette — the right one takes effect automatically when next-themes toggles between modes. Selection happens in components/reading-settings.tsx via a 3-up grid of swatch buttons (each swatch is a paint-chip-style square with the palette's background colour plus a small primary-coloured dot in the corner).

Text Selection Toolbar

  • Global floating toolbar appears on text selection across all reading pages (except RTFs)
  • Desktop: floating pill above selection; Mobile: fixed bottom bar
  • Actions: Ask Kautilya (Phase II), Share, Copy (with source reference), More
  • Share: modal (desktop) / bottom drawer (mobile) — share to feed, copy highlight link
  • More: popover (desktop) / bottom drawer (mobile) — bookmark, save to collection, copy link, mention
  • Copy appends source reference: title, chapter/verse, highlight URL
  • All toolbar surfaces use dark contrast treatment (bg-black, border-white/10)
  • Kautilya lotus icon for the AI companion button
  • On iOS Safari mobile, selection is saved/restored when drawers open/close to prevent focus conflicts
  • Selected text encoded as compact URL fragment: ?hl=prefix§suffix§length
  • Uses § delimiter (safe for Sanskrit, English, and all Vedic text)
  • Short selections (≤8 words) use full text as prefix
  • Incoming ?hl= URLs: text is found in DOM, wrapped in <mark>, scrolled to, highlighted permanently
  • TextHighlighter component runs on page load via useSearchParams
  • Server-side extractMatchedText() searches paragraphs for OG image quote extraction
  • Case-insensitive matching for both DOM highlighting and server-side text extraction
  • Cross-paragraph matching via position mapping (spaces inserted at element boundaries)
  • HTML-entity regex must be strict. extractMatchedText() strips HTML entities from paragraph strings before searching. The regex matches only real entity shapes — named (&amp;), decimal (&#39;), and hex (&#x27;) — not the loose &[^;]+; pattern, which would pair a stray & (e.g. "24 & 25 Oct") with a far-away ; (e.g. "my beloved son;") and eat everything between them, breaking OG quote extraction for any selection that spans such a stray ampersand. The browser-side findAndHighlightText is unaffected because it walks DOM text nodes that the browser has already decoded.
  • buildSearchResultUrl(resultUrl, snippetHtml) extracts the matched text from a search snippet's <mark> tags and appends ?hl=... to the result URL so the destination page highlights the match — same infrastructure as share links.

Dynamic OG Images

  • /api/og route generates 1200×630 PNG images using next/og (Satori)
  • Left panel: Prabhupāda image with quote mark and branding (from PNG)
  • Right panel: title (bold), detail, quote text, "Read more at prabhupadasays.org"
  • Font: Noto Sans Regular + Bold loaded from public/fonts/Noto_Sans/static/ — full IAST diacritical support
  • All 104 content pages have generateMetadata with contextual fallback descriptions
  • When ?hl= is present, the actual selected text is extracted from DB content for the OG quote

Audio Player

A site-wide floating audio player for transcription recordings (and future music library). Built with framer-motion for animations.

  • Desktop: Centered floating bar (max-width 680px) with cover image, track info, transport controls (prev/play/next), volume slider, source link, close button
  • Mobile: Full player shows two rows: track info row + centered transport controls
  • Auto-minimize: After 8 seconds of inactivity on both mobile and desktop, the player auto-minimizes to a floating pill. The minimized pill shows cover image with a circular progress ring and audio wave animation. Tap/click to expand.
  • X button behavior: Minimizes the player if audio is playing; stops playback entirely if audio is paused
  • Clicking outside the player: Minimizes it (does not stop playback)
  • Z-index: z-40 — below text selection toolbar (z-80), drawers (z-90), modals (z-100)
  • Hidden when selection toolbar is active on mobile to avoid overlap
  • Forced dark surface (bg-[#1c1c1e]) for accessibility contrast regardless of theme
  • Keyboard shortcuts: Space (play/pause), ←/→ (seek ±10s), ↑/↓ (volume), M (mute), Esc (close)
  • Progress seeker: 8px tall bar at top of player, click/drag to seek
  • Volume: Persisted to localStorage. Mobile relies on hardware volume buttons
  • framer-motion: Spring animations for enter/exit, AnimatePresence for minimize/expand transitions, motion.div for audio wave bars

Components:

  • components/audio-player/audio-context.tsx — React context + provider, manages <audio> element
  • components/audio-player/floating-player.tsx — Floating player UI
  • components/audio-player/audio-wave.tsx — Reusable animated audio wave bars
  • components/audio-player/index.ts — Barrel exports

The AudioPlayerProvider wraps the entire app in layout.tsx. The FloatingPlayer is rendered before SiteFooter.

  • Global footer on every page with © year, About link, Home link

Consistent Container Width

  • All pages use max-w-5xl for both header and body containers

Environment Variables

BFF_URL=http://localhost:8080          # Go BFF API server URL (default: http://localhost:8080)
ALLOW_RTF=true                         # Enable RTF viewer (development only)
NEXT_PUBLIC_SITE_URL=https://prabhupadasays.org  # Public site URL for OG images

No new vars are needed for the frontend search feature — search goes through the BFF, which has TYPESENSE_URL and TYPESENSE_API_KEY configured server-side.


How to Run

# 1. Start the Go BFF server (required — all data comes from the BFF)
cd spsays-bff && go run cmd/api/main.go    # Starts on :8080 by default

# 2. Development (from knowledgebase/)
npm run dev                                # Next.js dev server with Turbopack

# 3. Build
npx next build

# Extraction & seeding scripts are in extractor/ — see extractor/scripts/

Note

: No npx prisma generate, npx prisma db push, or MongoDB connection string needed in the frontend. The BFF server must be running for any page to load data.


Verse JSON Schema (BG/SB/CC/SB1962/ISO/NOI)

{
  "book_title": "Śrīmad-Bhāgavatam",
  "canto": 1,
  "chapter": 1,
  "verse": 1,
  "verse_end": null,
  "reference": "SB 1.1.1",
  "devanagari": "ॐ नमो भगवते वासुदेवाय\nजन्माद्यस्य यतो ...",
  "bengali": null,
  "transliteration": "om namo bhagavate vāsudevāya\njanmādy asya yato ...",
  "synonyms": [
    { "word": "om", "meanings": ["O my Lord"] },
    { "word": "namaḥ", "meanings": ["offering my obeisances"] }
  ],
  "translation": "<b>I offer my obeisances unto Lord Śrī Kṛṣṇa...</b>",
  "purport": ["paragraph 1...", "paragraph 2..."]
}

Prose Chapter JSON Schema

{
  "book": "ek",
  "chapter": 1,
  "title": "Choosing Human and Animal Lives",
  "type": "chapter",
  "paragraphs": ["<b>heading</b>", "body text...", "<div style=\"text-align:center\"><i>verse</i></div>"]
}

Intro Segment JSON Schema

{
  "slug": "preface",
  "title": "Preface",
  "type": "intro-segment",
  "book": "ek",
  "order": 2,
  "paragraphs": ["paragraph 1...", "paragraph 2..."]
}

Essay JSON Schema

{
  "slug": "krishna-reservoir-of-pleasure",
  "title": "Krishna The Reservoir Of Pleasure",
  "author": "His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda",
  "year": 1966,
  "group": "two-essays",
  "order": 1,
  "paragraphs": ["paragraph 1...", "paragraph 2..."]
}

Glossary JSON Schema

{
  "title": "Glossary of Vedic Terms",
  "description": "A comprehensive glossary...",
  "totalEntries": 2749,
  "letters": ["A", "B", "C", ...],
  "entries": [
    {
      "term": "Ācārya",
      "definition": "a spiritual master who teaches by his own example...",
      "definition_html": "a spiritual master who teaches by his own example..."
    }
  ]
}

Glossary is browsed via /glossary/{letter} routes (A-Z). Each letter page shows terms grouped by base letter (diacritical variants mapped: Ā→A, Ś→S, Ṛ→R, etc.). Single entry view with prev/next navigation.

Transcription JSON Schema

{
  "slug": "720911sb-los-angeles",
  "name": "Śrīmad-Bhāgavatam 1.2.6",
  "type": "lecture",
  "date": "September 11, 1972",
  "year": 1972,
  "month": 9,
  "day": 11,
  "place": "Los Angeles",
  "context": null,
  "reference": "SB 1.2.6",
  "durationMinutes": 45,
  "participants": ["Prabhupāda", "Devotees"],
  "audioUrl": "https://s3.prabhupadasays.com/sp-audio/...",
  "content": [
    {
      "text": "So Kṛṣṇa consciousness movement...",
      "format": "paragraph",
      "alignment": "left",
      "speaker": "Prabhupāda"
    },
    {
      "text": "<i>oṁ namo bhagavate...</i>",
      "format": "verse",
      "alignment": "center",
      "speaker": null
    }
  ]
}

Content block formats: "paragraph" (body text), "verse" (centered italic), "quote" (translation/purport), "heading" (TEXT/TRANSLATION/PURPORT labels), "note" (editorial notes).


Known Issues

  1. BFF dependency — The knowledgebase requires the Go BFF server to be running; all pages will fail to load data if the BFF is down.

  2. SB verse numbering: 5 duplicate verse reference tags in the HTML. Fixed by using the TEXT N header as authoritative.

  3. RTF routes gated: /rtfs only accessible when ALLOW_RTF=true.

  4. scripts/ excluded from tsconfig: Prevents TypeScript build errors from seed scripts.

  5. CSS zoom for font scaling: zoom property works in Chrome/Safari/Edge but may not work in older Firefox (< 126). A transform: scale() fallback is provided via @supports not (zoom: 1.1).

  6. Transcription BSON fallback — 1 document stores content as contentJson string due to BSON wire protocol limits; getTranscription() transparently parses it

  7. Empty transcriptions — 30 entries have no English text content (Hindi/Bengali recordings); the UI shows "No transcription available" with the audio player still accessible

  8. Audio player z-index — Player is hidden when text selection toolbar is active on mobile to avoid overlap

  9. Search depends on Typesense — Search depends on Typesense being available via the BFF; if Typesense is down, search will return empty results but the rest of the site is unaffected.

  10. Palette FOUC on first load — Palette data attributes are applied via the applyClasses effect after first paint. A user with a non-default palette will see a brief flash of the default palette before their preference kicks in. Could be fixed by injecting an inline script in layout.tsx that reads localStorage before hydration (next-themes does this for the .dark class). Not yet implemented.