.jlr[data-jlr-root]{
–bg:#0f1318; –panel:#141a22; –panel2:#10151c;
–text:#e9eef6; –muted:#aeb8c8; –border:rgba(233,238,246,.14);
–shadow:0 18px 60px rgba(0,0,0,.35);
–r:18px;
–fs:18px;
–zoom:1;
background:transparent !important;
color:var(–text);
display:block;
font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial;
font-size:var(–fs);
}
.jlr[data-jlr-root][data-theme=”light”]{
–bg:#fff; –panel:#f2f4f7; –panel2:#fff;
–text:#14161a; –muted:#5b6472; –border:rgba(20,22,26,.12);
–shadow:0 18px 50px rgba(0,0,0,.12);
}
.jlr[data-jlr-root] *{box-sizing:border-box;}
.jlr[data-jlr-root] .jlr-shell{
background:var(–bg);
border:1px solid var(–border);
border-radius:var(–r);
box-shadow:var(–shadow);
overflow:hidden;
max-width:1200px;
margin:18px auto;
}
.jlr__top{
display:flex; justify-content:space-between; align-items:center; gap:12px;
padding:14px 14px 10px;
background:linear-gradient(180deg, rgba(255,255,255,.04), transparent);
border-bottom:1px solid var(–border);
}
.jlr__brand{display:flex; gap:12px; align-items:center; min-width:0;}
.jlr__mark{
width:44px; height:44px; border-radius:14px;
display:grid; place-items:center;
background:var(–panel2);
border:1px solid var(–border);
letter-spacing:.12em; font-size:12px;
}
.jlr__title{font-weight:800;}
.jlr__subtitle{color:var(–muted); font-size:12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:60vw;}
.jlr__topActions{display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end; align-items:center;}
.jlr__boot{
border:1px solid var(–border);
border-radius:999px;
padding:6px 10px;
font-size:11px;
color:var(–muted);
background:rgba(255,255,255,.04);
}
.jlr__boot–ok{ color:#7ee7a8; }
.jlr__boot–bad{ color:#ffb4b4; }
.jlr__btn{
border:1px solid var(–border);
background:var(–text);
color:var(–panel2);
border-radius:999px;
padding:10px 12px;
cursor:pointer;
font-size:13px;
line-height:1;
}
.jlr[data-theme=”dark”] .jlr__btn{ background:var(–text); color:#0b0d10; }
.jlr__btn–ghost{ background:transparent; color:var(–text); }
.jlr__btn:hover{ filter:brightness(1.05); }
.jlr__btn:active{ transform:translateY(1px); }
.jlr__controls{
padding:12px 14px 14px;
background:var(–panel);
border-bottom:1px solid var(–border);
}
.jlr.is-controls-collapsed .jlr__controls{ display:none; }
.jlr__grid{
display:grid;
grid-template-columns:220px 220px 1fr 190px;
gap:10px;
}
.jlr__grid2{
display:grid;
grid-template-columns:1fr 260px auto;
gap:10px;
margin-top:10px;
align-items:end;
}
.jlr__field{display:flex; flex-direction:column; gap:6px; min-width:0;}
.jlr__field–grow{min-width:260px;}
.jlr__field–compact{min-width:180px;}
.jlr__label{font-size:11px; color:var(–muted); text-transform:uppercase; letter-spacing:.08em;}
.jlr__select,.jlr__input{
width:100%;
border-radius:14px;
border:1px solid var(–border);
background:var(–panel2);
color:var(–text);
padding:10px 12px;
outline:none;
font-size:14px;
}
.jlr[data-theme=”light”] .jlr__select,
.jlr[data-theme=”light”] .jlr__input{ background:#fff; color:var(–text); }
.jlr__input–center{text-align:center;}
.jlr__pageRow{display:flex; gap:8px; align-items:center;}
.jlr__pageRow .jlr__btn{padding:10px 11px;}
.jlr__chip{
display:inline-flex; align-items:center; justify-content:center;
padding:6px 10px;
border:1px solid var(–border);
border-radius:999px;
background:rgba(0,0,0,.10);
min-width:56px;
font-size:12px;
color:var(–muted);
}
.jlr__policy{
margin-top:10px;
padding:10px 12px;
border:1px solid var(–border);
border-radius:14px;
background:rgba(255,255,255,.04);
color:var(–muted);
font-size:12px;
line-height:1.4;
}
.jlr__policyTag{
display:inline-block; margin-left:8px;
padding:3px 8px; border:1px solid var(–border);
border-radius:999px; font-size:11px;
background:rgba(255,255,255,.04);
}
.jlr__status{ margin-top:10px; color:var(–muted); font-size:12px; min-height:16px; }
.jlr__main{ padding:14px; }
.jlr__readerHead{
display:flex; justify-content:space-between; gap:10px; align-items:center;
padding:10px 12px;
border:1px solid var(–border);
border-radius:16px;
background:rgba(255,255,255,.03);
}
.jlr__crumb{ color:var(–muted); font-size:12px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.jlr__readerBtns{ display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end; }
.jlr.is-reader-mode .jlr__top,
.jlr.is-reader-mode .jlr__footer,
.jlr.is-reader-mode .jlr__controls,
.jlr.is-reader-mode .jlr__readerHead{ display:none; }
.jlr.is-reader-mode .jlr__main{ padding:0; }
.jlr__viewerWrap{ display:flex; justify-content:center; padding:12px 0 0; }
.jlr__viewerInner{ width:100%; max-width:1000px; }
.jlr__frame{
display:block !important;
width:calc(100% / var(–zoom)) !important;
height:calc(min(78vh, 980px) / var(–zoom));
min-height:560px;
border:1px solid var(–border);
border-radius:18px;
background:var(–panel2);
transform:scale(var(–zoom));
transform-origin:0 0;
}
.jlr__fallback{
margin-top:10px;
padding:10px 12px;
border:1px dashed var(–border);
border-radius:14px;
color:var(–muted);
background:rgba(255,255,255,.03);
font-size:12px;
}
.jlr__mobileNav{
display:none;
margin-top:12px;
gap:10px;
justify-content:space-between;
}
.jlr__mobileNav .jlr__btn{ flex:1; padding:14px 12px; font-size:14px; }
.jlr__footer{
padding:12px 14px 14px;
border-top:1px solid var(–border);
background:var(–panel);
display:flex; gap:10px; justify-content:space-between; flex-wrap:wrap;
}
.jlr__credit{display:flex; gap:10px; align-items:center; color:var(–muted); font-size:12px;}
.jlr__dot{opacity:.7;}
.jlr__link{color:inherit; text-decoration:underline; text-underline-offset:3px;}
.jlr__diag{
color:var(–muted);
font-size:11px;
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,”Liberation Mono”,monospace;
white-space:pre-wrap;
}
@media (max-width:980px){
.jlr__grid{grid-template-columns:1fr 1fr;}
.jlr__grid2{grid-template-columns:1fr 1fr;}
.jlr__mobileNav{ display:flex; }
.jlr__frame{ min-height:520px; height:75vh; }
}
@media (max-width:560px){
.jlr__grid{grid-template-columns:1fr;}
.jlr__grid2{grid-template-columns:1fr;}
.jlr__subtitle{max-width:72vw;}
}
(() => {
“use strict”;
const README_CDN = “
https://cdn.jsdelivr.net/gh/jainqq-org/JLOR@main/README.md” ;;
const README_RAW = “
https://raw.githubusercontent.com/jainqq-org/JLOR/main/README.md” ;;
// Built-in fallback so dropdowns NEVER empty
const BUILTIN = [
{ l1:”Shwetambar Agams”, l2:”Ang Agams”, title:”Acharang Sutra Part 1″, srno:”007646″, verified:true },
{ l1:”Shwetambar Agams”, l2:”Ang Agams”, title:”Acharang Sutra Part 2″, srno:”007647″, verified:true },
{ l1:”Shwetambar Agams”, l2:”Ang Agams”, title:”Sutrakritanga Sutra”, srno:”011060″, verified:true },
{ l1:”Shwetambar Agams”, l2:”Ang Agams”, title:”Sthananga Sutra Part 1″, srno:”002905″, verified:true },
{ l1:”Shwetambar Agams”, l2:”Ang Agams”, title:”Sthananga Sutra Part 2″, srno:”002906″, verified:true },
{ l1:”Shwetambar Agams”, l2:”Ang Agams”, title:”Samvayang Sutra”, srno:”002488″, verified:true },
{ l1:”Shwetambar Agams”, l2:”Ang Agams”, title:”Bhagavati Sutra”, srno:”002902″, verified:true },
// Examples (hidden unless AI toggle ON)
{ l1:”Other Texts”, l2:”AI/Unverified”, title:”Tattvartha Sutra (catalog item)”, srno:”001916″, verified:false },
{ l1:”Anuyogas and Digambar Texts”, l2:”Pratham Anuyoga (Stories)”, title:”Padmapurana Part 1″, srno:”001822″, verified:false },
];
// Storage safe wrapper
const mem = Object.create(null);
const store = {
get(k, fb = null) {
try {
const v = localStorage.getItem(k);
return v == null ? (k in mem ? mem[k] : fb) : v;
} catch {
return (k in mem ? mem[k] : fb);
}
},
set(k, v) {
const s = String(v);
mem[k] = s;
try { localStorage.setItem(k, s); } catch {}
}
};
// Boot + handle Gutenberg rerenders
const ROOT_SEL = ‘.jlr[data-jlr-root]’;
const initAll = () => document.querySelectorAll(ROOT_SEL).forEach((root, idx) => initOne(root, idx));
function boot() {
initAll();
const mo = new MutationObserver(() => initAll());
mo.observe(document.documentElement, { childList: true, subtree: true });
}
if (document.readyState === “loading”) document.addEventListener(“DOMContentLoaded”, boot, { once: true });
else boot();
function initOne(ROOT, idx) {
if (ROOT.dataset.jlrInit === “1”) return;
ROOT.dataset.jlrInit = “1”;
ROOT.dataset.jlrInst = ROOT.dataset.jlrInst || String(idx + 1);
const inst = ROOT.dataset.jlrInst;
const LS = {
theme: `jlr_theme_${inst}`,
font: `jlr_font_${inst}`,
ai: `jlr_ai_${inst}`,
last: `jlr_last_${inst}`,
bm: `jlr_bm_${inst}`,
readme: `jlr_readme_${inst}`,
readmeAt: `jlr_readmeAt_${inst}`,
};
const q = (k) => ROOT.querySelector(`[data-jlr=”${k}”]`);
const els = {
boot: q(“boot”),
diag: q(“diag”),
status: q(“status”),
policyTag: q(“policyTag”),
crumb: q(“crumb”),
fallback: q(“fallback”),
l1: q(“l1”),
l2: q(“l2”),
text: q(“text”),
search: q(“search”),
page: q(“page”),
frame: q(“frame”),
// Buttons
panelToggle: q(“panelToggle”),
theme: q(“theme”),
aminus: q(“aminus”),
aplus: q(“aplus”),
bookmark: q(“bookmark”),
share: q(“share”),
open: q(“open”),
load: q(“load”),
prev: q(“prev”),
next: q(“next”),
prev2: q(“prev2”),
next2: q(“next2”),
aiToggle: q(“aiToggle”),
aiChip: q(“aiChip”),
readerMode: q(“readerMode”),
};
const log = (m) => { if (els.diag) els.diag.textContent += (els.diag.textContent ? “\n” : “”) + m; };
const status = (m) => { if (els.status) els.status.textContent = m || “”; };
if (!els.l1 || !els.l2 || !els.text || !els.load || !els.aiToggle || !els.frame) {
if (els.boot) { els.boot.textContent = “JS: BLOCKED”; els.boot.classList.add(“jlr__boot–bad”); }
return;
}
if (els.boot) { els.boot.textContent = “JS: RUNNING”; els.boot.classList.add(“jlr__boot–ok”); }
let catalog = […BUILTIN];
const clamp = (n, a, b) => Math.max(a, Math.min(b, n));
const exploreUrl = (srno, page) => `https://jainqq.org/explore/${srno}/${page}`;
const clear = (sel) => sel.replaceChildren();
const addOpt = (sel, value, label) => {
const o = document.createElement(“option”);
o.value = value;
o.textContent = label;
sel.appendChild(o);
};
const aiEnabled = () => store.get(LS.ai, “0”) === “1”;
const setAI = (v) => {
store.set(LS.ai, v ? “1” : “0”);
els.aiToggle.setAttribute(“aria-pressed”, v ? “true” : “false”);
if (els.aiChip) els.aiChip.textContent = v ? “On” : “Off”;
};
const setTheme = (t) => { ROOT.dataset.theme = t; store.set(LS.theme, t); };
const setFont = (px) => {
const v = Math.max(14, Math.min(24, px));
ROOT.style.setProperty(“–fs”, `${v}px`);
const zoom = Math.max(0.85, Math.min(1.25, v / 18));
ROOT.style.setProperty(“–zoom”, String(zoom)); // zoom iframe too
store.set(LS.font, String(v));
};
function currentItem() {
const srno = els.text.value;
return catalog.find(x => x.srno === srno) || null;
}
function applyPolicyTag(item) {
if (!els.policyTag) return;
if (!item) { els.policyTag.textContent = “—”; return; }
els.policyTag.textContent = item.verified ? “✅ Verified (non-AI)” : “⚠ AI/Unverified”;
}
function filterCatalog() {
const qtxt = (els.search?.value || “”).trim().toLowerCase();
const allowAI = aiEnabled();
return catalog.filter(it => {
if (!allowAI && !it.verified) return false;
if (qtxt && !it.title.toLowerCase().includes(qtxt)) return false;
return true;
});
}
function buildHierarchy(items) {
const h = {};
for (const it of items) {
(h[it.l1] ||= {});
(h[it.l1][it.l2] ||= []).push(it);
}
for (const a of Object.keys(h)) {
for (const b of Object.keys(h[a])) {
h[a][b].sort((x,y) => (x.verified === y.verified ? x.title.localeCompare(y.title) : (x.verified ? -1 : 1)));
}
}
return h;
}
function repopulate(keep = {}) {
const filtered = filterCatalog();
const hier = buildHierarchy(filtered);
const l1s = Object.keys(hier).sort((a,b)=>a.localeCompare(b));
clear(els.l1);
(l1s.length ? l1s : [“No results”]).forEach(k => addOpt(els.l1, k === “No results” ? “” : k, k));
els.l1.value = (keep.l1 && l1s.includes(keep.l1)) ? keep.l1 : (l1s[0] || “”);
const l2s = Object.keys(hier[els.l1.value] || {}).sort((a,b)=>a.localeCompare(b));
clear(els.l2);
(l2s.length ? l2s : [“No results”]).forEach(k => addOpt(els.l2, k === “No results” ? “” : k, k));
els.l2.value = (keep.l2 && l2s.includes(keep.l2)) ? keep.l2 : (l2s[0] || “”);
const list = hier[els.l1.value]?.[els.l2.value] || [];
clear(els.text);
if (!list.length) addOpt(els.text, “”, “No texts”);
else list.forEach(it => addOpt(els.text, it.srno, `${it.verified ? “✅ ” : “⚠ “}${it.title}`));
els.text.value = (keep.srno && list.some(x => x.srno === keep.srno)) ? keep.srno : (list[0]?.srno || “”);
applyPolicyTag(currentItem());
status(aiEnabled() ? `Showing verified + AI/unverified (${filtered.length})` : `Showing verified only (${filtered.length})`);
}
function encodeState(st) {
return btoa(unescape(encodeURIComponent(JSON.stringify(st))));
}
function decodeState(s) {
return JSON.parse(decodeURIComponent(escape(atob(s))));
}
function getState() {
return {
l1: els.l1.value,
l2: els.l2.value,
srno: els.text.value,
page: String(parseInt(els.page?.value || “1”, 10) || 1),
ai: store.get(LS.ai, “0”)
};
}
function setFrame(url) {
els.frame.src = url;
// basic iframe-block detect: if it never loads, fallback stays visible; user can Open
if (els.fallback) els.fallback.hidden = true;
status(“Loading…”);
}
function loadReader() {
const item = currentItem();
if (!item || !item.srno) { status(“Select a text.”); return; }
const page = clamp(parseInt(els.page?.value || “1”, 10) || 1, 1, 999999);
if (els.page) els.page.value = String(page);
const url = exploreUrl(item.srno, page);
setFrame(url);
applyPolicyTag(item);
if (els.crumb) els.crumb.textContent = `${item.title} • SR ${item.srno} • Page ${page}`;
status(item.verified ? “✅ Verified” : “⚠ AI/Unverified”);
store.set(LS.last, JSON.stringify(getState()));
}
function bump(delta) {
const p = clamp((parseInt(els.page?.value || “1”, 10) || 1) + delta, 1, 999999);
if (els.page) els.page.value = String(p);
loadReader();
}
// iframe load / error hints
els.frame.addEventListener(“load”, () => {
status(“Loaded.”);
if (els.fallback) els.fallback.hidden = true;
});
// Some browsers don’t fire error reliably for iframe cross-site blocks.
// We show fallback if the iframe still looks empty after a short delay.
const showFallbackSoon = () => {
if (!els.fallback) return;
setTimeout(() => { els.fallback.hidden = false; }, 1500);
};
// Event delegation so buttons never “lose” handlers on re-render
ROOT.addEventListener(“click”, async (ev) => {
const btn = ev.target.closest(“button[data-jlr]”);
if (!btn) return;
const key = btn.getAttribute(“data-jlr”);
if (key === “panelToggle”) ROOT.classList.toggle(“is-controls-collapsed”);
else if (key === “readerMode”) ROOT.classList.toggle(“is-reader-mode”);
else if (key === “theme”) setTheme(ROOT.dataset.theme === “dark” ? “light” : “dark”);
else if (key === “aminus”) setFont((parseInt(store.get(LS.font, “18”), 10) || 18) – 1);
else if (key === “aplus”) setFont((parseInt(store.get(LS.font, “18”), 10) || 18) + 1);
else if (key === “aiToggle”) { setAI(!aiEnabled()); repopulate({ l1: els.l1.value, l2: els.l2.value, srno: els.text.value }); }
else if (key === “prev” || key === “prev2”) bump(-1);
else if (key === “next” || key === “next2”) bump(1);
else if (key === “load”) loadReader();
else if (key === “open”) {
const url = els.frame.src || exploreUrl(currentItem()?.srno || “007646”, parseInt(els.page?.value || “1”, 10) || 1);
window.open(url, “_blank”, “noopener,noreferrer”);
}
else if (key === “bookmark”) {
store.set(LS.bm, JSON.stringify(getState()));
status(“Bookmarked.”);
setTimeout(() => status(“”), 900);
}
else if (key === “share”) {
const st = getState();
const url = new URL(window.location.href);
url.searchParams.set(“jlr”, encodeState(st));
const shareUrl = url.toString();
try {
if (navigator.share) await navigator.share({ title: “Jain Scripture Reader”, url: shareUrl });
else if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(shareUrl);
else window.prompt(“Copy link:”, shareUrl);
status(“Share link ready.”);
setTimeout(() => status(“”), 900);
} catch {}
}
});
ROOT.addEventListener(“change”, (ev) => {
const el = ev.target.closest(“[data-jlr]”);
if (!el) return;
const key = el.getAttribute(“data-jlr”);
if (key === “l1”) repopulate({ l1: els.l1.value });
if (key === “l2”) repopulate({ l1: els.l1.value, l2: els.l2.value });
if (key === “text”) applyPolicyTag(currentItem());
});
ROOT.addEventListener(“input”, (ev) => {
const el = ev.target.closest(“[data-jlr]”);
if (!el) return;
const key = el.getAttribute(“data-jlr”);
if (key === “search”) repopulate({ l1: els.l1.value, l2: els.l2.value, srno: els.text.value });
});
// README hydrate (no GitHub API)
async function fetchWithTimeout(url, ms = 20000) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), ms);
try {
const r = await fetch(url, { signal: ctrl.signal, cache: “no-store” });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return await r.text();
} finally { clearTimeout(t); }
}
function parseReadme(md) {
const out = [];
const lines = String(md || “”).split(/\r?\n/);
let l1 = “JLOR”, l2 = “General”;
for (const line of lines) {
const h2 = line.match(/^##\s+(.+)\s*$/);
if (h2) { l1 = h2[1].trim(); l2 = “General”; continue; }
const h3 = line.match(/^###\s+(.+)\s*$/);
if (h3) { l2 = h3[1].trim(); continue; }
const link = line.match(/\[([^\]]+)\]\((https?:\/\/jainqq\.org\/explore\/(\d{1,6})\/1)\)/);
if (!link) continue;
out.push({
l1, l2,
title: link[1].trim(),
srno: String(link[3] || “”).padStart(6, “0”),
verified: /✅\s*Verified/i.test(line)
});
}
const seen = new Set();
return out.filter(x => {
const k = `${x.srno}|${x.title}|${x.l1}|${x.l2}|${x.verified}`;
if (seen.has(k)) return false;
seen.add(k);
return true;
});
}
async function hydrateCatalog() {
const cached = store.get(LS.readme, “”);
const cachedAt = parseInt(store.get(LS.readmeAt, “0”), 10) || 0;
const fresh = (Date.now() – cachedAt) `${it.srno}|${it.title}|${it.l1}|${it.l2}|${it.verified}`;
const existing = new Set(catalog.map(key));
let added = 0;
for (const it of parsed) {
const k = key(it);
if (!existing.has(k)) { catalog.push(it); existing.add(k); added++; }
}
log(`Catalog merged +${added} (total ${catalog.length})`);
repopulate({ l1: els.l1.value, l2: els.l2.value, srno: els.text.value });
}
// Init UI
try {
const savedTheme = store.get(LS.theme, null);
if (savedTheme === “dark” || savedTheme === “light”) setTheme(savedTheme);
else setTheme(window.matchMedia && window.matchMedia(“(prefers-color-scheme: dark)”).matches ? “dark” : “light”);
setFont(parseInt(store.get(LS.font, “18”), 10) || 18);
setAI(store.get(LS.ai, “0”) === “1”);
// Restore state from URL > bookmark > last
try {
const u = new URL(window.location.href);
const enc = u.searchParams.get(“jlr”);
if (enc) {
const st = decodeState(enc);
setAI(st.ai === “1”);
repopulate({ l1: st.l1, l2: st.l2, srno: st.srno });
if (els.page) els.page.value = st.page || “1”;
} else {
const bm = store.get(LS.bm, “”);
const last = store.get(LS.last, “”);
const src = bm || last;
if (src) {
const st = JSON.parse(src);
setAI(st.ai === “1”);
repopulate({ l1: st.l1, l2: st.l2, srno: st.srno });
if (els.page) els.page.value = st.page || “1”;
} else {
repopulate({ l1:”Shwetambar Agams”, l2:”Ang Agams”, srno:”007646″ });
}
}
} catch {
repopulate({ l1:”Shwetambar Agams”, l2:”Ang Agams”, srno:”007646″ });
}
hydrateCatalog().finally(() => {
loadReader();
showFallbackSoon();
log(“Ready ✅”);
});
} catch (e) {
if (els.boot) { els.boot.textContent = “JS: ERROR”; els.boot.classList.add(“jlr__boot–bad”); }
log(`Init error: ${String(e?.message || e)}`);
}
}
})();
Major Section
Division
Text
AI / Unverified
Off
Search titles
Page
Load
Translation Notice:
AI makes mistakes and can generate misinformation.
“The Seeker’s Lamp.com” and “Hochmah Media INC” are not responsible for misinformation in the AI Generated Translations.
Always cross-check with trusted human editions and primary sources.
—
Viewer blocked. Your browser/site is blocking embeds. Use Open to read in a new tab.
Prev
Next