chore: update project context

This commit is contained in:
gosangam
2026-05-11 14:53:03 +05:30
parent 7294d9e15b
commit 179b7d8862
+58 -5
View File
@@ -535,11 +535,17 @@ SSR and DS have parent chapters with sub-chapters stored as individual `prose_ch
### 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`
- Applied via CSS classes on `<html>` element
- 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
@@ -547,6 +553,14 @@ SSR and DS have parent chapters with sub-chapters stored as individual `prose_ch
- 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** (`/search` page) — Typo-tolerant, diacritic-insensitive full-text search across all content.
@@ -564,10 +578,33 @@ SSR and DS have parent chapters with sub-chapters stored as individual `prose_ch
- **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
@@ -575,9 +612,21 @@ SSR and DS have parent chapters with sub-chapters stored as individual `prose_ch
- Glossary page: full-text search across terms and definitions
- Uses Unicode NFD normalization + manual IAST character stripping
### Color Palette
- **Light mode ("Aged Parchment")**: Warm cream backgrounds, deep sepia text, saffron/ochre primary
- **Dark mode ("Ancient Manuscript")**: Deep warm brown backgrounds, parchment text, golden saffron primary
### 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)
@@ -599,6 +648,8 @@ SSR and DS have parent chapters with sub-chapters stored as individual `prose_ch
- 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)
@@ -810,3 +861,5 @@ Content block formats: `"paragraph"` (body text), `"verse"` (centered italic), `
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.