Bhagavad Gita

.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&#8221;; 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://&#8221;)) { 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(); })();
Bhagavad Gita Reader
English-first • Full chapters • Bookmarks • Share w/ translation reference • Dark/Light • Mobile
Theme: Auto Theme: Light Theme: Dark
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.

Leave a Comment