.gitaR{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;line-height:1.35}
.gitaR *{box-sizing:border-box}
.gitaR{
–bg:#fff;–panel:#f6f7fb;–text:#0f172a;–muted:#64748b;
–border:rgba(15,23,42,.12);–shadow:0 10px 32px rgba(2,6,23,.09);
–accent:#2563eb;–danger:#dc2626;–chip:rgba(100,116,139,.08);–radius:16px
}
.gitaR[data-theme=”dark”]{
–bg:#0b1220;–panel:#0f172a;–text:#e5e7eb;–muted:#94a3b8;
–border:rgba(148,163,184,.18);–shadow:0 14px 40px rgba(0,0,0,.35);
–accent:#60a5fa;–danger:#f87171;–chip:rgba(148,163,184,.10)
}
.gitaR-card{background:var(–bg);color:var(–text);border:1px solid var(–border);border-radius:var(–radius);box-shadow:var(–shadow);overflow:hidden}
.gitaR-header{display:flex;gap:12px;align-items:center;justify-content:space-between;padding:14px;background:linear-gradient(180deg,var(–panel),var(–bg));border-bottom:1px solid var(–border)}
.gitaR-title{font-weight:900;font-size:18px}
.gitaR-sub{color:var(–muted);font-size:12px;margin-top:2px}
.gitaR-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}
.gitaR-bar{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;padding:12px 14px;border-bottom:1px solid var(–border);background:var(–bg)}
.gitaR-field{display:flex;flex-direction:column;gap:6px;min-width:220px}
.gitaR-field-grow{flex:1;min-width:260px}
.gitaR-label{font-size:12px;font-weight:800;color:var(–muted)}
.gitaR-help{font-size:12px;color:var(–muted)}
.gitaR-help code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,”Courier New”,monospace;font-size:12px}
.gitaR-status{font-size:12px;color:var(–muted);min-height:18px}
.gitaR-input,.gitaR-select{background:var(–bg);color:var(–text);border:1px solid var(–border);border-radius:12px;padding:10px 12px;outline:none}
.gitaR-input:focus,.gitaR-select:focus{border-color:rgba(37,99,235,.55);box-shadow:0 0 0 3px rgba(37,99,235,.18)}
.gitaR-check{display:flex;gap:8px;align-items:center;color:var(–muted);font-size:13px}
.gitaR-btn{appearance:none;border:1px solid transparent;border-radius:12px;padding:10px 12px;font-weight:800;font-size:14px;cursor:pointer;background:var(–accent);color:#fff}
.gitaR-btn:disabled{opacity:.6;cursor:not-allowed}
.gitaR-btn-ghost{background:transparent;color:var(–text);border:1px solid var(–border)}
.gitaR-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.gitaR-layout{display:grid;grid-template-columns:360px 1fr;gap:12px;padding:12px 14px;background:var(–panel)}
.gitaR-pane{background:var(–bg);border:1px solid var(–border);border-radius:14px;overflow:hidden;min-height:420px}
.gitaR-paneHead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px;border-bottom:1px solid var(–border);background:linear-gradient(180deg,var(–panel),var(–bg))}
.gitaR-paneTitle{font-weight:900}
.gitaR-paneSub{font-size:12px;color:var(–muted);margin-top:2px}
.gitaR-paneBody{padding:12px}
.gitaR-divider{height:1px;background:var(–border);margin:10px 0}
.gitaR-readerHead{align-items:center}
.gitaR-readerRight{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
.gitaR-chip{font-size:12px;color:var(–muted);background:var(–chip);border:1px solid var(–border);padding:8px 10px;border-radius:999px}
.gitaR-progressWrap{padding:10px 12px;border-bottom:1px solid var(–border);background:var(–bg)}
.gitaR-progressBar{height:10px;border-radius:999px;background:rgba(100,116,139,.18);overflow:hidden;border:1px solid var(–border)}
.gitaR-progressFill{height:100%;width:0%}
.gitaR-progressText{margin-top:6px;font-size:12px;color:var(–muted)}
.gitaR-content{font-size:16px;line-height:1.55}
.gitaR-block{border:1px solid var(–border);background:linear-gradient(180deg,var(–panel),var(–bg));border-radius:14px;padding:12px;margin-bottom:12px}
.gitaR-blockTitle{font-weight:900;margin:0 0 6px}
.gitaR-kicker{font-size:12px;color:var(–muted);margin:0 0 8px}
.gitaR-text{white-space:pre-wrap;margin:0}
.gitaR-verseTag{display:inline-block;font-size:12px;color:var(–muted);border:1px solid var(–border);background:var(–chip);padding:4px 10px;border-radius:999px;margin-bottom:8px}
.gitaR-empty{border:1px dashed var(–border);border-radius:14px;padding:14px;background:rgba(100,116,139,.05);color:var(–muted)}
.gitaR-emptyTitle{font-weight:900;color:var(–text);margin-bottom:6px}
.gitaR-emptyText{margin-top:4px}
.gitaR-cite{border-top:1px solid var(–border);padding:12px;background:var(–bg)}
.gitaR-citeTitle{font-weight:900;margin-bottom:6px}
.gitaR-citeBody{font-size:12px;color:var(–muted)}
.gitaR-citeBody code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,”Courier New”,monospace;font-size:12px}
/* Optional: hide connect/test buttons (auto-connect is default) */
.gitaR [data-gitaR-connect],
.gitaR [data-gitaR-test]{
display:none !important;
}
.gitaR-drawer{border-top:1px solid var(–border);background:var(–bg);padding:12px 14px}
.gitaR-drawerHead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}
.gitaR-drawerTitle{font-weight:900}
.gitaR-drawerBody{display:flex;flex-direction:column;gap:8px}
.gitaR-drawerFoot{display:flex;justify-content:flex-end;margin-top:10px}
.gitaR-bmItem{border:1px solid var(–border);border-radius:12px;padding:10px;background:var(–bg)}
.gitaR-bmTitle{font-weight:900;font-size:14px}
.gitaR-bmMeta{color:var(–muted);font-size:12px;margin-top:2px}
@media (max-width: 900px){
.gitaR-layout{grid-template-columns:1fr}
.gitaR-field{min-width:unset;width:100%}
}
(function () {
“use strict”;
const LS_PREFS = “gita_reader_prefs_v3”;
const LS_BM = “gita_reader_bookmarks_v2”;
const DEFAULT_BASE = “
https://vedicscriptures.github.io”;
const $ = (sel, root) => (root || document).querySelector(sel);
const $$ = (sel, root) => Array.from((root || document).querySelectorAll(sel));
const safeJson = (s, f) => { try { return JSON.parse(s); } catch { return f; } };
const clamp = (n, a, b) => Math.max(a, Math.min(b, n));
const normBase = (u) => (u || “”).trim().replace(/\/+$/, “”);
function loadPrefs() { return safeJson(localStorage.getItem(LS_PREFS) || “{}”, {}); }
function savePrefs(p) { try { localStorage.setItem(LS_PREFS, JSON.stringify(p)); } catch {} }
function loadBm() { return safeJson(localStorage.getItem(LS_BM) || “[]”, []); }
function saveBm(bm) { try { localStorage.setItem(LS_BM, JSON.stringify(bm)); } catch {} }
function getTheme(prefs) {
const prefersDark = window.matchMedia && window.matchMedia(“(prefers-color-scheme: dark)”).matches;
if (prefs.theme === “auto”) return prefersDark ? “dark” : “light”;
return prefs.theme || “light”;
}
async function fetchJson(url, signal) {
const res = await fetch(url, { headers: { “Accept”: “application/json” }, signal });
const text = await res.text();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`);
try { return JSON.parse(text); }
catch { throw new Error(`Non-JSON response: ${text.slice(0, 160)}`); }
}
async function fetchJsonWithFallback(baseUrl, paths, signal) {
let lastErr = null;
for (const p of paths) {
try { return await fetchJson(baseUrl + p, signal); }
catch (e) { lastErr = e; }
}
throw lastErr || new Error(“Fetch failed”);
}
function getEnglish(verseJson, translatorKey) {
const obj = verseJson && verseJson[translatorKey];
if (obj && obj.et) return String(obj.et).trim();
const keys = [“siva”, “purohit”, “adi”, “gambir”, “san”];
for (const k of keys) {
const o = verseJson && verseJson[k];
if (o && o.et) return String(o.et).trim();
}
return “”;
}
function getCommentary(verseJson, translatorKey) {
const obj = verseJson && verseJson[translatorKey];
if (!obj) return “”;
return String(obj.ec || obj.commentary || “”).trim();
}
function getAuthorLabel(verseJson, translatorKey) {
const obj = verseJson && verseJson[translatorKey];
if (obj && obj.author) return String(obj.author).trim();
return “”;
}
function buildShareLink({ mode, ch, v, tr }) {
const u = new URL(window.location.href);
u.searchParams.set(“gita_mode”, mode);
u.searchParams.set(“gita_ch”, String(ch));
if (mode === “verse”) u.searchParams.set(“gita_v”, String(v));
else u.searchParams.delete(“gita_v”);
u.searchParams.set(“gita_tr”, tr);
return u.toString();
}
async function shareOrCopy({ title, text, url }) {
try {
if (navigator.share) {
await navigator.share({ title, text, url });
return { ok: true, how: “share” };
}
} catch {}
const payload = `${title}\n\n${text}\n\n${url}`;
try {
await navigator.clipboard.writeText(payload);
return { ok: true, how: “copy” };
} catch {
const ta = document.createElement(“textarea”);
ta.value = payload;
ta.style.position = “fixed”;
ta.style.left = “-9999px”;
document.body.appendChild(ta);
ta.select();
try { document.execCommand(“copy”); } catch {}
document.body.removeChild(ta);
return { ok: true, how: “copy-legacy” };
}
}
function addPreconnect(url) {
try {
const u = new URL(url);
const origin = u.origin;
if (document.querySelector(`link[rel=”preconnect”][href=”${origin}”]`)) return;
const l1 = document.createElement(“link”);
l1.rel = “dns-prefetch”;
l1.href = origin;
document.head.appendChild(l1);
const l2 = document.createElement(“link”);
l2.rel = “preconnect”;
l2.href = origin;
l2.crossOrigin = “anonymous”;
document.head.appendChild(l2);
} catch {}
}
function init(root) {
const prefs = {
baseUrl: DEFAULT_BASE,
theme: “auto”,
mode: “verse”,
showSans: true,
showTranslit: true,
showComm: false,
translator: “siva”,
chapter: 1,
verse: 1,
…loadPrefs()
};
const elTheme = $(“[data-gitaR-theme]”, root);
const elBase = $(“[data-gitaR-base]”, root);
const elStatus = $(“[data-gitaR-status]”, root);
const elMode = $(“[data-gitaR-mode]”, root);
const elVerseField = $(“[data-gitaR-verseField]”, root);
const elShowSans = $(“[data-gitaR-showSans]”, root);
const elShowTranslit = $(“[data-gitaR-showTranslit]”, root);
const elShowComm = $(“[data-gitaR-showComm]”, root);
const elChapter = $(“[data-gitaR-chapter]”, root);
const elVerse = $(“[data-gitaR-verse]”, root);
const elTranslator = $(“[data-gitaR-translator]”, root);
const elPrev = $(“[data-gitaR-prev]”, root);
const elNext = $(“[data-gitaR-next]”, root);
const elBm = $(“[data-gitaR-bm]”, root);
const elShareVerse = $(“[data-gitaR-shareVerse]”, root);
const elShareChapter = $(“[data-gitaR-shareChapter]”, root);
const elJump = $(“[data-gitaR-jump]”, root);
const elJumpBtn = $(“[data-gitaR-jumpBtn]”, root);
const elTitle = $(“[data-gitaR-title]”, root);
const elSub = $(“[data-gitaR-sub]”, root);
const elChip = $(“[data-gitaR-chip]”, root);
const elContent = $(“[data-gitaR-content]”, root);
const elNavSub = $(“[data-gitaR-navSub]”, root);
const elOpenBm = $(“[data-gitaR-openBm]”, root);
const elBmDrawer = $(“[data-gitaR-bmDrawer]”, root);
const elBmList = $(“[data-gitaR-bmList]”, root);
const elCloseBm = $(“[data-gitaR-closeBm]”, root);
const elClearBm = $(“[data-gitaR-clearBm]”, root);
const elCancel = $(“[data-gitaR-cancel]”, root);
const elProgressWrap = $(“[data-gitaR-progressWrap]”, root);
const elProgressFill = $(“[data-gitaR-progressFill]”, root);
const elProgressText = $(“[data-gitaR-progressText]”, root);
const elCite = $(“[data-gitaR-cite]”, root);
const elCiteBody = $(“[data-gitaR-citeBody]”, root);
let chapters = [];
let versesCountByChapter = {};
let currentVerseJson = null;
let currentChapterVerses = [];
let abortCtl = null;
const verseCache = new Map();
const chapterCache = new Map();
let connectInFlight = false;
let baseEditTimer = null;
function applyTheme() { root.setAttribute(“data-theme”, getTheme(prefs)); }
function setStatus(msg, isErr) {
if (!elStatus) return;
elStatus.textContent = msg || “”;
elStatus.style.color = isErr ? “var(–danger)” : “var(–muted)”;
}
function base() {
const b = normBase(prefs.baseUrl || DEFAULT_BASE);
if (!b) throw new Error(“Missing API Base URL”);
if (location.protocol === “https:” && b.startsWith(“
http://”)) {
throw new Error(“Your site is HTTPS, but API URL is HTTP. Use HTTPS.”);
}
return b;
}
function setModeUI() {
const isChapter = prefs.mode === “chapter”;
if (elVerseField) elVerseField.style.display = isChapter ? “none” : “”;
if (elJump) elJump.disabled = isChapter;
if (elJumpBtn) elJumpBtn.disabled = isChapter;
if (elShareVerse) elShareVerse.disabled = isChapter;
}
function setProgress(on, pct, text) {
if (!elProgressWrap) return;
elProgressWrap.hidden = !on;
if (elCancel) elCancel.hidden = !on;
if (on) {
const p = Math.max(0, Math.min(100, pct || 0));
elProgressFill.style.width = p + “%”;
elProgressFill.style.background = “var(–accent)”;
elProgressText.textContent = text || “Loading…”;
}
}
function cancelLoading() {
if (abortCtl) {
abortCtl.abort();
abortCtl = null;
setProgress(false);
setStatus(“Canceled.”);
}
}
function chapterMeta(chNum) {
return chapters.find(x => x.chapter_number === chNum) || null;
}
function renderChapterSelect() {
elChapter.innerHTML = “”;
elChapter.appendChild(new Option(“Select chapter…”, “”));
chapters.forEach(ch => {
const n = ch.chapter_number;
const label = `Chapter ${n}: ${ch.translation || ch.meaning?.en || ch.name || “”}`;
elChapter.appendChild(new Option(label, String(n)));
});
elChapter.disabled = false;
elVerse.disabled = false;
}
function renderVerseSelect(chNum) {
const maxV = versesCountByChapter[chNum] || 1;
elVerse.innerHTML = “”;
for (let i = 1; i <= maxV; i++) elVerse.appendChild(new Option(`Verse ${i}`, String(i)));
}
function escapeHtml(s) {
return String(s || "").replace(/[&”‘]/g, (c) => ({
“&”: “&”, “”: “>”, “\””: “"”, “‘”: “'”
}[c]));
}
function renderTranslationReference({ ch, v, mode, authorLabel }) {
const trName = elTranslator.options[elTranslator.selectedIndex]?.text || prefs.translator;
const endpoint = mode === “verse”
? `/slok/${ch}/${v}`
: `/slok/${ch}/1 … /slok/${ch}/${versesCountByChapter[ch] || “N”}`;
elCite.hidden = false;
elCiteBody.innerHTML =
`English translator:
${escapeHtml(trName)}` +
(authorLabel ? ` (API author label:
${escapeHtml(authorLabel)})` : “”) +
`
Source:
${escapeHtml(normBase(prefs.baseUrl) || DEFAULT_BASE)}${escapeHtml(endpoint)}`;
}
function block(title, kicker, text) {
const d = document.createElement(“div”);
d.className = “gitaR-block”;
d.innerHTML = `
`;
d.querySelector(“.gitaR-blockTitle”).textContent = title;
d.querySelector(“.gitaR-kicker”).textContent = kicker;
d.querySelector(“.gitaR-text”).textContent = String(text || “”);
return d;
}
function blockInner(title, text) {
const d = document.createElement(“div”);
d.className = “gitaR-block”;
d.style.margin = “10px 0 0”;
d.innerHTML = `
`;
d.querySelector(“.gitaR-blockTitle”).textContent = title;
d.querySelector(“.gitaR-text”).textContent = String(text || “”);
return d;
}
async function loadVerseSingle(ch, v, trKey, signal) {
const cacheKey = `${trKey}|${ch}|${v}`;
if (verseCache.has(cacheKey)) return verseCache.get(cacheKey);
const json = await fetchJsonWithFallback(base(), [`/slok/${ch}/${v}`, `/slok/${ch}/${v}/`], signal);
verseCache.set(cacheKey, json);
return json;
}
async function loadCurrentVerse() {
const ch = parseInt(elChapter.value, 10);
const v = parseInt(elVerse.value, 10);
if (!ch || !v) return;
prefs.chapter = ch;
prefs.verse = v;
prefs.translator = elTranslator.value;
prefs.showSans = !!elShowSans.checked;
prefs.showTranslit = !!elShowTranslit.checked;
prefs.showComm = !!elShowComm.checked;
savePrefs(prefs);
setStatus(“Loading verse…”);
try {
abortCtl = new AbortController();
const json = await loadVerseSingle(ch, v, prefs.translator, abortCtl.signal);
abortCtl = null;
currentVerseJson = json;
currentChapterVerses = [];
renderReader();
setStatus(“Ready ✓”);
} catch (e) {
abortCtl = null;
if (String(e.name) === “AbortError”) return;
setStatus(“Load failed: ” + e.message, true);
}
}
async function loadFullChapter() {
const ch = parseInt(elChapter.value, 10);
if (!ch) return;
prefs.chapter = ch;
prefs.translator = elTranslator.value;
prefs.showSans = !!elShowSans.checked;
prefs.showTranslit = !!elShowTranslit.checked;
prefs.showComm = !!elShowComm.checked;
savePrefs(prefs);
const count = versesCountByChapter[ch] || 0;
if (!count) {
setStatus(“No verse count available for this chapter.”, true);
return;
}
const cacheKey = `${prefs.translator}|${ch}`;
if (chapterCache.has(cacheKey)) {
currentChapterVerses = chapterCache.get(cacheKey);
currentVerseJson = null;
renderReader();
setStatus(`Loaded Chapter ${ch} ✓`);
return;
}
setStatus(`Loading Chapter ${ch} (${count} verses)…`);
setProgress(true, 0, `Loading Chapter ${ch}… 0 / ${count}`);
abortCtl = new AbortController();
const signal = abortCtl.signal;
try {
const results = new Array(count);
let done = 0;
const CONCURRENCY = 6;
let nextV = 1;
async function worker() {
while (nextV <= count) {
const v = nextV++;
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
const json = await loadVerseSingle(ch, v, prefs.translator, signal);
results[v – 1] = json;
done++;
const pct = Math.round((done / count) * 100);
setProgress(true, pct, `Loading Chapter ${ch}… ${done} / ${count}`);
}
}
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, count) }, worker));
abortCtl = null;
setProgress(false);
currentChapterVerses = results.filter(Boolean);
currentVerseJson = null;
chapterCache.set(cacheKey, currentChapterVerses);
renderReader();
setStatus(`Chapter ${ch} loaded ✓`);
} catch (e) {
abortCtl = null;
setProgress(false);
if (String(e.name) === "AbortError") {
setStatus("Canceled.");
return;
}
setStatus("Chapter load failed: " + e.message, true);
}
}
async function refreshReader() {
if (prefs.mode === "chapter") return loadFullChapter();
return loadCurrentVerse();
}
function renderReader() {
const ch = parseInt(elChapter.value, 10) || prefs.chapter || 1;
const v = parseInt(elVerse.value, 10) || prefs.verse || 1;
const meta = chapterMeta(ch);
const chapTitle = meta ? (meta.translation || meta.meaning?.en || meta.name || `Chapter ${ch}`) : `Chapter ${ch}`;
elTitle.textContent = chapTitle;
elSub.textContent = meta?.meaning?.en ? meta.meaning.en : "Bhagavad Gita";
const trName = elTranslator.options[elTranslator.selectedIndex]?.text || prefs.translator;
elChip.textContent = (prefs.mode === "verse")
? `Verse mode • Ch ${ch} • Verse ${v} • ${trName}`
: `Chapter mode • Ch ${ch} • ${trName}`;
elContent.innerHTML = "";
if (prefs.mode === "verse") {
const verse = currentVerseJson || {};
const english = getEnglish(verse, prefs.translator);
const commentary = getCommentary(verse, prefs.translator);
const authorLabel = getAuthorLabel(verse, prefs.translator);
renderTranslationReference({ ch, v, mode: "verse", authorLabel });
elContent.appendChild(block("English", "Translation", english || "(No English translation returned for this translator.)"));
if (prefs.showSans) elContent.appendChild(block("Sanskrit", "Devanagari", (verse.slok || "").toString().trim() || "(No Sanskrit text found.)"));
if (prefs.showTranslit) elContent.appendChild(block("Transliteration", "IAST-ish", (verse.transliteration || "").toString().trim() || "(No transliteration found.)"));
if (prefs.showComm) elContent.appendChild(block("Commentary", "If available for selected translator", commentary || "(No commentary returned for this translator.)"));
if (meta?.summary?.en) elContent.appendChild(block("Chapter Summary", "Overview", meta.summary.en));
return;
}
if (!currentChapterVerses.length) {
elContent.innerHTML = `
No chapter loaded yet
Pick a chapter to load the full chapter.
`;
elCite.hidden = true;
return;
}
const first = currentChapterVerses[0] || {};
const authorLabel = getAuthorLabel(first, prefs.translator);
renderTranslationReference({ ch, v: 1, mode: “chapter”, authorLabel });
elContent.appendChild(block(“Full Chapter”, `Chapter ${ch} • ${trName}`, meta?.summary?.en || “Scroll to read all verses below.”));
currentChapterVerses.forEach((verseJson, idx) => {
const verseNo = (verseJson && verseJson.verse) ? verseJson.verse : (idx + 1);
const english = getEnglish(verseJson, prefs.translator);
const commentary = getCommentary(verseJson, prefs.translator);
const slok = (verseJson.slok || “”).toString().trim();
const translit = (verseJson.transliteration || “”).toString().trim();
const wrap = document.createElement(“div”);
wrap.className = “gitaR-block”;
const tag = document.createElement(“div”);
tag.className = “gitaR-verseTag”;
tag.textContent = `Verse ${verseNo}`;
wrap.appendChild(tag);
wrap.appendChild(blockInner(“English”, english || “(No English translation returned for this translator.)”));
if (prefs.showSans) wrap.appendChild(blockInner(“Sanskrit”, slok || “(No Sanskrit text found.)”));
if (prefs.showTranslit) wrap.appendChild(blockInner(“Transliteration”, translit || “(No transliteration found.)”));
if (prefs.showComm) wrap.appendChild(blockInner(“Commentary”, commentary || “(No commentary returned for this translator.)”));
elContent.appendChild(wrap);
});
}
async function connect(auto = false) {
if (connectInFlight) return;
connectInFlight = true;
cancelLoading();
prefs.baseUrl = normBase(elBase.value) || DEFAULT_BASE;
savePrefs(prefs);
addPreconnect(prefs.baseUrl || DEFAULT_BASE);
setStatus(auto ? “Auto-loading…” : “Connecting…”);
try {
const data = await fetchJsonWithFallback(base(), [“/chapters”, “/chapters/”]);
chapters = Array.isArray(data) ? data : [];
versesCountByChapter = {};
chapters.forEach(ch => { versesCountByChapter[ch.chapter_number] = ch.verses_count; });
renderChapterSelect();
setStatus(“Loaded ✓”);
if (elNavSub) elNavSub.textContent = “Pick Chapter → (Verse in verse mode)”;
const ch = clamp(parseInt(prefs.chapter, 10) || 1, 1, 18);
elChapter.value = String(ch);
renderVerseSelect(ch);
const maxV = versesCountByChapter[ch] || 1;
const v = clamp(parseInt(prefs.verse, 10) || 1, 1, maxV);
elVerse.value = String(v);
setModeUI();
await refreshReader();
} catch (e) {
setStatus(“Auto-load failed: ” + e.message, true);
} finally {
connectInFlight = false;
}
}
async function test() {
setStatus(“Testing…”);
prefs.baseUrl = normBase(elBase.value) || DEFAULT_BASE;
savePrefs(prefs);
try {
addPreconnect(prefs.baseUrl || DEFAULT_BASE);
await fetchJsonWithFallback(base(), [“/chapters”, “/chapters/”]);
setStatus(“Test OK ✓”);
} catch (e) {
setStatus(“Test failed: ” + e.message, true);
}
}
function prevNext(delta) {
const ch = parseInt(elChapter.value, 10);
if (!ch) return;
if (prefs.mode === “chapter”) {
const nextCh = clamp(ch + delta, 1, 18);
elChapter.value = String(nextCh);
renderVerseSelect(nextCh);
elVerse.value = “1”;
refreshReader();
return;
}
const maxV = versesCountByChapter[ch] || 1;
let v = parseInt(elVerse.value, 10) || 1;
v += delta;
if (v 1) {
elChapter.value = String(ch – 1);
renderVerseSelect(ch – 1);
elVerse.value = String(versesCountByChapter[ch – 1] || 1);
} else elVerse.value = “1”;
} else if (v > maxV) {
if (ch < 18) {
elChapter.value = String(ch + 1);
renderVerseSelect(ch + 1);
elVerse.value = "1";
} else elVerse.value = String(maxV);
} else elVerse.value = String(v);
refreshReader();
}
function renderBmList() {
const bm = loadBm();
elBmList.innerHTML = "";
if (!bm.length) {
elBmList.innerHTML = `
No bookmarks yet. Click Add Bookmark.
`;
return;
}
bm.forEach((b, idx) => {
const div = document.createElement(“div”);
div.className = “gitaR-bmItem”;
div.innerHTML = `
`;
div.querySelector(“.gitaR-bmTitle”).textContent = b.label;
div.querySelector(“.gitaR-bmMeta”).textContent = b.meta;
const [openBtn, rmBtn] = div.querySelectorAll(“button”);
openBtn.addEventListener(“click”, async () => {
elBase.value = b.baseUrl || DEFAULT_BASE;
prefs.baseUrl = elBase.value;
prefs.mode = b.mode || “verse”;
prefs.chapter = b.chapter;
prefs.verse = b.verse || 1;
prefs.translator = b.translator || “siva”;
savePrefs(prefs);
elBmDrawer.hidden = true;
elMode.value = prefs.mode;
setModeUI();
await connect(true);
elTranslator.value = prefs.translator;
elChapter.value = String(prefs.chapter);
renderVerseSelect(prefs.chapter);
elVerse.value = String(prefs.verse || 1);
await refreshReader();
});
rmBtn.addEventListener(“click”, () => {
const next = loadBm().filter((_, i) => i !== idx);
saveBm(next);
renderBmList();
});
elBmList.appendChild(div);
});
}
function addBm() {
const ch = parseInt(elChapter.value, 10);
const v = parseInt(elVerse.value, 10) || 1;
if (!ch) { setStatus(“Pick a chapter first.”, true); return; }
const translator = elTranslator.value;
const meta = chapterMeta(ch);
const chapName = meta?.translation || meta?.meaning?.en || `Chapter ${ch}`;
const mode = prefs.mode;
const label = (mode === “chapter”) ? `Ch ${ch} (Full Chapter)` : `Ch ${ch} : ${v}`;
const bm = loadBm();
const entry = {
baseUrl: normBase(elBase.value) || DEFAULT_BASE,
mode,
chapter: ch,
verse: (mode === “verse”) ? v : null,
translator,
label,
meta: `${chapName} • ${elTranslator.options[elTranslator.selectedIndex].text} • ${mode === “chapter” ? “chapter mode” : “verse mode”}`
};
const sig = `${entry.baseUrl}::${mode}::${ch}::${entry.verse || “all”}::${translator}`;
if (bm.some(x => `${x.baseUrl}::${x.mode}::${x.chapter}::${x.verse || “all”}::${x.translator}` === sig)) {
setStatus(“Already bookmarked ✓”);
elBmDrawer.hidden = false;
renderBmList();
return;
}
bm.unshift(entry);
saveBm(bm.slice(0, 200));
setStatus(“Bookmarked ✓”);
elBmDrawer.hidden = false;
renderBmList();
}
function jumpTo() {
if (prefs.mode !== “verse”) { setStatus(“Jump works in verse mode.”, true); return; }
const raw = (elJump.value || “”).trim();
const m = raw.match(/^(\d{1,2})\s*[:.]\s*(\d{1,3})$/);
if (!m) { setStatus(“Use format like 2:47”, true); return; }
const ch = clamp(parseInt(m[1], 10), 1, 18);
const maxV = versesCountByChapter[ch] || 1;
const v = clamp(parseInt(m[2], 10), 1, maxV);
elChapter.value = String(ch);
renderVerseSelect(ch);
elVerse.value = String(v);
refreshReader();
}
async function shareVerse() {
if (prefs.mode !== “verse” || !currentVerseJson) {
setStatus(“Open a verse first (verse mode).”, true);
return;
}
const ch = parseInt(elChapter.value, 10);
const v = parseInt(elVerse.value, 10);
const trKey = elTranslator.value;
const trName = elTranslator.options[elTranslator.selectedIndex]?.text || trKey;
const english = getEnglish(currentVerseJson, trKey);
const authorLabel = getAuthorLabel(currentVerseJson, trKey);
const url = buildShareLink({ mode: “verse”, ch, v, tr: trKey });
const title = `Bhagavad Gita — ${ch}:${v}`;
const excerpt = (english || “”).replace(/\s+/g, ” “).trim().slice(0, 240);
const text =
`${excerpt}${excerpt ? “…” : “”}\n` +
`Translation: ${trName}${authorLabel ? ` (author label: ${authorLabel})` : “”}\n` +
`Source: Vedic Scriptures Bhagavad Gita API`;
const r = await shareOrCopy({ title, text, url });
setStatus(r.ok ? (r.how === “share” ? “Shared ✓” : “Copied ✓”) : “Share failed”, !r.ok);
}
async function shareChapter() {
const ch = parseInt(elChapter.value, 10);
if (!ch) { setStatus(“Pick a chapter first.”, true); return; }
const trKey = elTranslator.value;
const trName = elTranslator.options[elTranslator.selectedIndex]?.text || trKey;
let authorLabel = “”;
if (currentChapterVerses && currentChapterVerses[0]) authorLabel = getAuthorLabel(currentChapterVerses[0], trKey);
if (!authorLabel && currentVerseJson) authorLabel = getAuthorLabel(currentVerseJson, trKey);
const meta = chapterMeta(ch);
const chapTitle = meta?.translation || meta?.meaning?.en || `Chapter ${ch}`;
const url = buildShareLink({ mode: “chapter”, ch, v: 1, tr: trKey });
const title = `Bhagavad Gita — Chapter ${ch}`;
const text =
`${chapTitle}\n` +
`Mode: Full chapter\n` +
`Translation: ${trName}${authorLabel ? ` (author label: ${authorLabel})` : “”}\n` +
`Source: Vedic Scriptures Bhagavad Gita API`;
const r = await shareOrCopy({ title, text, url });
setStatus(r.ok ? (r.how === “share” ? “Shared ✓” : “Copied ✓”) : “Share failed”, !r.ok);
}
// Events
elTheme.addEventListener(“change”, () => { prefs.theme = elTheme.value; savePrefs(prefs); applyTheme(); });
if (elCancel) elCancel.addEventListener(“click”, cancelLoading);
elMode.addEventListener(“change”, () => {
cancelLoading();
prefs.mode = elMode.value;
savePrefs(prefs);
setModeUI();
refreshReader();
});
elShowSans.addEventListener(“change”, () => { prefs.showSans = elShowSans.checked; savePrefs(prefs); renderReader(); });
elShowTranslit.addEventListener(“change”, () => { prefs.showTranslit = elShowTranslit.checked; savePrefs(prefs); renderReader(); });
elShowComm.addEventListener(“change”, () => { prefs.showComm = elShowComm.checked; savePrefs(prefs); renderReader(); });
elTranslator.addEventListener(“change”, () => {
cancelLoading();
prefs.translator = elTranslator.value;
savePrefs(prefs);
verseCache.clear();
chapterCache.clear();
refreshReader();
});
elChapter.addEventListener(“change”, () => {
cancelLoading();
const ch = parseInt(elChapter.value, 10);
if (!ch) return;
renderVerseSelect(ch);
elVerse.value = “1”;
prefs.chapter = ch;
prefs.verse = 1;
savePrefs(prefs);
refreshReader();
});
elVerse.addEventListener(“change”, () => { if (prefs.mode === “verse”) refreshReader(); });
elPrev.addEventListener(“click”, () => prevNext(-1));
elNext.addEventListener(“click”, () => prevNext(1));
elBm.addEventListener(“click”, addBm);
if (elShareVerse) elShareVerse.addEventListener(“click”, shareVerse);
if (elShareChapter) elShareChapter.addEventListener(“click”, shareChapter);
elOpenBm.addEventListener(“click”, () => { elBmDrawer.hidden = false; renderBmList(); });
elCloseBm.addEventListener(“click”, () => { elBmDrawer.hidden = true; });
elClearBm.addEventListener(“click”, () => { saveBm([]); renderBmList(); });
elJumpBtn.addEventListener(“click”, jumpTo);
// Auto reconnect if Base URL edited (debounced)
elBase.addEventListener(“input”, () => {
if (baseEditTimer) clearTimeout(baseEditTimer);
baseEditTimer = setTimeout(() => {
prefs.baseUrl = normBase(elBase.value) || DEFAULT_BASE;
savePrefs(prefs);
verseCache.clear();
chapterCache.clear();
connect(true);
}, 700);
});
// Auto theme refresh
if (window.matchMedia) {
const mql = window.matchMedia(“(prefers-color-scheme: dark)”);
const onChange = () => { if ((prefs.theme || “auto”) === “auto”) applyTheme(); };
if (mql.addEventListener) mql.addEventListener(“change”, onChange);
else if (mql.addListener) mql.addListener(onChange);
}
// Init UI values
elBase.value = normBase(prefs.baseUrl) || DEFAULT_BASE;
elTheme.value = prefs.theme || “auto”;
elMode.value = prefs.mode || “verse”;
elTranslator.value = prefs.translator || “siva”;
elShowSans.checked = prefs.showSans !== false;
elShowTranslit.checked = prefs.showTranslit !== false;
elShowComm.checked = !!prefs.showComm;
applyTheme();
setModeUI();
setStatus(“Auto-loading…”);
// ✅ AUTO-CONNECT ON PAGE LOAD (no click)
Promise.resolve().then(() => connect(true));
// Share buttons
if (elShareVerse) elShareVerse.addEventListener(“click”, shareVerse);
if (elShareChapter) elShareChapter.addEventListener(“click”, shareChapter);
// Hidden connect/test buttons still supported if you unhide them
const elConnect = $(“[data-gitaR-connect]”, root);
const elTest = $(“[data-gitaR-test]”, root);
if (elConnect) elConnect.addEventListener(“click”, () => connect(false));
if (elTest) elTest.addEventListener(“click”, test);
// Drawer clear
if (elClearBm) elClearBm.addEventListener(“click”, () => { saveBm([]); renderBmList(); });
}
function boot() { $$(“[data-gitaR]”).forEach(init); }
if (document.readyState === “loading”) document.addEventListener(“DOMContentLoaded”, boot);
else boot();
})();
API Base URL (HTTPS)
Default: https://vedicscriptures.github.io (endpoints: /chapters, /slok/:ch/:sl)
Mode
Verse (single)
Chapter (full)
Chapter mode loads every verse in the chapter.
Display
Show Sanskrit
Show transliteration
Show commentary (if available)
Loading…
Auto-connecting to scripture API
Loading…
If this takes long, your builder may be blocking scripts. Try WPCode footer.