PageSourceSearch

https://kingjamesbiblereader.com/

html kingjamesbiblereader.com collected 2026-10-02 01:33:21 UTC 49,260 bytes, 903 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3 <!-- Reconciled with GitHub main (commit b5563525): site-wide Home/Read/Gospel/Resources tabs, sidepanel shell, compact reader + search UI. -->
4 <head>
5  
5<script>
6   (function () {
7  var proto = XMLHttpRequest.prototype;
8  var open = proto.open, setRequestHeader = proto.setRequestHeader, send = proto.send;
9  var TOKEN_KEY = 'base44_access_token';
10  var storedTokenHeader = function () {
11    try { return 'Bearer ' + localStorage.getItem(TOKEN_KEY); } catch (e) { return null; }
12  };
13  var forgetStoredToken = function () {
14    try { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem('token'); } catch (e) {}
15  };
16
17  proto.open = function (method, url) {
18    this.__b44IsAuthCheck = /\/entities\/User\/me(\?|$)/.test(url);
19    return open.apply(this, arguments);
20  };
21
22  proto.setRequestHeader = function (name, value) {
23    if (this.__b44IsAuthCheck && name.toLowerCase() === 'authorization' && value === storedTokenHeader()) {
24      this.__b44SentToken = value;
25    }
26    return setRequestHeader.call(this, name, value);
27  };
28
29  proto.send = function () {
30    this.addEventListener('loadend', function () {
31      var rejectedTokenStillStored = this.status === 401 && this.__b44SentToken === storedTokenHeader();
32      if (rejectedTokenStillStored) forgetStoredToken();
33    });
34    return send.apply(this, arguments);
35  };
36})();
37  </script>
37
38  <meta charset="utf-8"/>
39  <!-- Pre-empt the vite plugin's unhandledrejection handler, which crashes
40         ("Cannot read properties of undefined (reading 'match')") when the
41         rejected value is not an Error object. This capturing-phase listener
42         runs before the plugin's handler and stops propagation for non-Error
43         rejections so the plugin never sees them. -->
44  
44<script>
45   (function () {
46        window.addEventListener('unhandledrejection', function (event) {
47          var reason = event && event.reason;
48          // The vite plugin's handler accesses reason.stack.match(...). If the
49          // rejection is not a real Error (no string .stack), the plugin crashes
50          // with "Cannot read properties of undefined (reading 'match')". Stop
51          // propagation for any non-Error rejection so the plugin never sees it.
52          if (!reason || typeof reason.stack !== 'string') {
53            event.preventDefault();
54            event.stopImmediatePropagation();
55            return;
56          }
57          // Swallow SW not-found rejections (preview sandbox can't serve /sw.js)
58          var msg = (typeof reason.message === 'string') ? reason.message : '';
59          if (/Failed to (update|register) a ServiceWorker/i.test(msg)) {
60            event.preventDefault();
61            event.stopImmediatePropagation();
62            return;
63          }
64        }, true);
65      })();
66  </script>
66
67  <!-- Detect Internet Explorer / very old browsers and redirect to the legacy
68         reader BEFORE any modern JS runs (the React app can't run on IE). -->
69  
69<script>
70   (function () {
71        try {
72          var ua = navigator.userAgent || '';
73          // IE10/11 ("Trident") and older IE ("MSIE"). Not Edge.
74          var isIE = /Trident|MSIE/i.test(ua);
75          // A dedicated legacy domain should ALWAYS land on the server-rendered
76          // legacy reader, regardless of browser. Send every visitor straight
77          // to the function endpoint (which the React /legacy page only redirects
78          // to anyway).
79          var legacyHost = (window.location.hostname || '').toLowerCase();
80          var qsEarly = window.location.search || '';
81          var optedOutEarly = qsEarly.indexOf('nolegacy=1') !== -1;
82          var STATIC_LEGACY_FILE = 'https://media.base44.com/files/public/6a05d76723afe58d80c589e8/kjb-reader-legacy.html';
83          // Static file redirect removed — legacy domains now serve the function page directly.
84          // Only treat as "too old" if it's clearly NOT a modern browser.
85          // The bare Promise/fetch check can misfire in some embedded/in-app
86          // browsers, so we additionally require an old-IE-style userAgent.
87          var tooOld = isIE && ((typeof window.Promise === 'undefined') || (typeof window.fetch === 'undefined'));
88          // Parse query string manually (IE doesn't support URLSearchParams reliably)
89          var qs = window.location.search || '';
90          var optedOut = qs.indexOf('nolegacy=1') !== -1;
91          // A ?tab= param AT THE ROOT path ("/") means a legacy deep link
92          // (gospel/resources/about/debug) — route it to the server-rendered
93          // legacy reader. Only fires on "/" so it never interferes with the
94          // "/legacy" route's own ?tab= links/form submits.
95          var atRoot = window.location.pathname === '/' || window.location.pathname === '';
96          var wantsLegacyTab = atRoot && qs.indexOf('tab=') !== -1;
97
98          // Native app origins (capacitor://localhost — the iOS offline-fallback
99          // copy; Android always serves /legacy through the React route) can't
100          // reach the server-rendered function page, and offline they must not
101          // even try: the React /legacy page loads the identical page from the
102          // app bundle instead (see src/pages/LegacyReader.jsx). So on a
103          // capacitor: origin a /legacy visit falls through to the React route
104          // instead of redirecting to a path that would 404.
105          var isNativeCap = /^capacitor:/i.test((window.location.protocol || ''));
106
107          // A direct visit to "/legacy" always means the server-rendered Full
108          // Bible reader — send EVERY browser straight to the function endpoint
109          // (the React /legacy page can't run on IE and only redirects anyway).
110          if (window.location.pathname === '/legacy' && !optedOut && !isNativeCap) {
111            var lHost = window.location.hostname || '';
112            var lIsCustom = lHost.indexOf('base44.app') === -1 && lHost.indexOf('localhost') === -1;
113            var lOrigin = window.location.origin || (window.location.protocol + '//' + window.location.host);
114            if (lIsCustom) {
115              window.location.replace(lOrigin + '/functions/legacy');
116            } else {
117              var lAppId = null;
118              var lIdx = qs.indexOf('app_id=');
119              if (lIdx !== -1) {
120                var lPart = qs.substring(lIdx + 7);
121                var lAmp = lPart.indexOf('&');
122                lAppId = lAmp !== -1 ? lPart.substring(0, lAmp) : lPart;
123              }
124              if (!lAppId) { try { lAppId = localStorage.getItem('base44_app_id'); } catch (e) {} }
125              window.location.replace(lAppId ? '/api/apps/' + lAppId + '/functions/legacy' : '/functions/legacy');
126            }
127            return;
128          }
129          if (wantsLegacyTab && !optedOut) {
130            if (isNativeCap) {
131              // Same reasoning as above: the native offline copy renders the
132              // legacy reader from the app bundle via the React /legacy route.
133              window.location.replace('/legacy' + qs);
134              return;
135            }
136            var tHost = window.location.hostname || '';
137            var tIsCustom = tHost.indexOf('base44.app') === -1 && tHost.indexOf('localhost') === -1;
138            if (tIsCustom) {
139              window.location.replace(window.location.origin + '/legacy' + qs);
140            } else {
141              var tAppId = null;
142              var tIdx = qs.indexOf('app_id=');
143              if (tIdx !== -1) {
144                var tPart = qs.substring(tIdx + 7);
145                var tAmp = tPart.indexOf('&');
146                tAppId = tAmp !== -1 ? tPart.substring(0, tAmp) : tPart;
147              }
148              if (!tAppId) { try { tAppId = localStorage.getItem('base44_app_id'); } catch (e) {} }
149              window.location.replace((tAppId ? '/api/apps/' + tAppId + '/functions/legacy' : '/functions/legacy') + qs);
150            }
151            return;
152          }
153          if ((isIE || tooOld) && !optedOut) {
154            var host = window.location.hostname || '';
155            var isCustom = host.indexOf('base44.app') === -1 && host.indexOf('localhost') === -1;
156            // IE8 lacks window.location.origin — build it manually as a fallback.
157            var origin = window.location.origin || (window.location.protocol + '//' + window.location.host);
158            var legacyUrl;
159            if (isCustom) {
160              legacyUrl = origin + '/functions/legacy';
161            } else {
162              var appId = null;
163              var appIdx = qs.indexOf('app_id=');
164              if (appIdx !== -1) {
165                var appPart = qs.substring(appIdx + 7);
166                var ampIdx = appPart.indexOf('&');
167                appId = ampIdx !== -1 ? appPart.substring(0, ampIdx) : appPart;
168              }
169              if (!appId) {
170                try { appId = localStorage.getItem('base44_app_id'); } catch (e) {}
171              }
172              legacyUrl = appId
173                ? '/api/apps/' + appId + '/functions/legacy'
174                : '/functions/legacy';
175            }
176            // Force immediate redirect before any rendering
177            window.location.replace(legacyUrl);
178          }
179        } catch (e) {
180          // If detection fails on an old browser, redirect STRAIGHT to the
181          // server-rendered function (never the React /legacy page, which IE
182          // can't run). Use the origin path which works on the custom domain.
183          try {
184            window.location.replace(window.location.origin + '/functions/legacy');
185          } catch (e2) {
186            try { window.location.replace('/functions/legacy'); } catch (e3) {}
187          }
188        }
189      })();
190  </script>
190
191  <!-- Favicons: the same KJB Reader icon used by the PWA manifest, served
192         same-origin as static files in public/icons/ (kjb-icon-192/512
193         PNG assets) so it always resolves — never the dead cross-app media
194         reference from the previous app instance. Browsers prefer these
195         explicit <link rel="icon"> entries over the platform's default
196         /favicon.ico redirect. -->
197  <link href="/icons/kjb-icon-512.png" rel="icon" sizes="512x512" type="image/png"/>
198  <link href="/icons/kjb-icon-512.png" rel="shortcut icon" type="image/png"/>
199  <meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"/>
200  <!-- Capture beforeinstallprompt early (Chrome/Edge/Samsung/Android mobile) -->
201  
201<script>
202   (function() {
203        if (typeof window === 'undefined') return;
204        // Set up global deferred prompt storage immediately
205        window.kjbDeferredPrompt = null;
206        window.kjbPromptFired = false;
207        window.kjbPromptedThisSession = false;
208
209        // Capture beforeinstallprompt as early as possible (mobile + desktop).
210        // Chromium browsers (Chrome/Edge/Samsung Internet) only fire this event
211        // for an app that is NOT currently installed — so seeing it fire is the
212        // most reliable signal we have that any previously-remembered
213        // "installed" flag is now stale (e.g. the user uninstalled the PWA from
214        // their home screen/app list). Clearing it here is what makes the app
215        // automatically fall back to showing the Install button again, without
216        // the user having to manually reset anything.
217        window.addEventListener('beforeinstallprompt', function(e) {
218          e.preventDefault();
219          window.kjbDeferredPrompt = e;
220          window.kjbPromptFired = true;
221          window.kjbPromptedThisSession = true;
222          try { localStorage.removeItem('kjb-is-installed'); } catch (err) {}
223          console.log('[PWA] beforeinstallprompt captured - not currently installed, native prompt ready');
224          window.dispatchEvent(new Event('kjb-install-change'));
225          window.dispatchEvent(new Event('pwa-installable'));
226        });
227
228        // Fires the moment the user accepts the native install prompt (or
229        // installs via the browser's own address-bar/menu affordance).
230        window.addEventListener('appinstalled', function() {
231          console.log('[PWA] appinstalled event fired');
232          window.kjbDeferredPrompt = null;
233          window.kjbPromptFired = false;
234          try {
235            localStorage.setItem('kjb-is-installed', 'true');
236            // Don't re-nag the install/welcome prompt after a successful install
237            localStorage.setItem('kjb-prompt-dismissed', 'true');
238            localStorage.setItem('kjb-install-dismissed', 'true');
239          } catch (e) {}
240          // Let the open browser tab (useInstallPrompt / FirstLoadPrompt /
241          // InstallAppSection) flip to the "Installed" state immediately.
242          window.dispatchEvent(new Event('storage'));
243          window.dispatchEvent(new Event('kjb-install-change'));
244        });
245      })();
246  </script>
246
247  <!-- Allow caching for offline support -->
248  <meta content="public, max-age=3600, stale-while-revalidate=86400" http-equiv="Cache-Control"/>
249  <title>
250   KJB Reader
251  </title>
252  <!-- Preload critical resources -->
253  <link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600;700&amp;family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&amp;family=Dancing+Script:wght@300;400;500;600;700&amp;family=Great+Vibes&amp;family=Inter:wght@300;400;500;600;700&amp;family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700;1,900&amp;family=Comic+Neue:ital,wght@0,400;0,700;1,400&amp;display=block" rel="stylesheet"/>
254  <meta content="Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word of God." name="description"/>
255  <!-- Tells the browser/WebView which canvas colour to paint for the blank
256         page BEFORE any of our CSS applies — without it, the very first frame
257         is the browser's default white, which shows as a white flash on a
258         dark-mode launch. -->
259  <meta content="dark light" name="color-scheme"/>
260  <meta content="#0f1117" name="theme-color"/>
261  <meta content="yes" name="apple-mobile-web-app-capable"/>
262  <meta content="black-translucent" name="apple-mobile-web-app-status-bar-style"/>
263  <meta content="KJB Reader" name="apple-mobile-web-app-title"/>
264  <link href="/icons/kjb-icon-512.png" rel="apple-touch-icon" sizes="512x512"/>
265  <meta content="yes" name="mobile-web-app-capable"/>
266  <!-- Performance optimizations -->
267  <link crossorigin="" href="https://media.base44.com" rel="preconnect"/>
268  <link href="https://media.base44.com" rel="dns-prefetch"/>
269  <link crossorigin="" href="https://fonts.googleapis.com" rel="preconnect"/>
270  <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
271  <link as="image" href="https://media.base44.com/images/public/6a05d76723afe58d80c589e8/2279e016e_8e738d108_cfb4bf781_Untitled.png" rel="preload"/>
272  <link as="font" crossorigin="" href="/fonts/OpenDyslexic-regular.woff" rel="preload" type="font/woff"/>
273  <link as="font" crossorigin="" href="/fonts/OpenDyslexic-bold.woff" rel="preload" type="font/woff"/>
274  <link as="font" crossorigin="" href="/fonts/OpenDyslexic-italic.woff" rel="preload" type="font/woff"/>
275  <meta content="Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word of God.,  " name="description"/>
276  <meta content="Bible, King James Bible, KJB, Pure Cambridge Edition, offline Bible, Christian, scripture" name="keywords"/>
277  <!-- Social sharing preview (Open Graph / Twitter Card) -->
278  <meta content="KJB Reader" property="og:title"/>
279  <meta content="Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word of God.,  " property="og:description"/>
280  <meta content="https://media.base44.com/images/public/6a8011c360ff52dad38eb2f3/e85b31b2e_10fb7857-d202-4070-98ce-c14094770136.jpg/v1/fill/w_1200,h_630/e85b31b2e_10fb7857-d202-4070-98ce-c14094770136.jpg" property="og:image"/>
281  <meta content="1200" property="og:image:width"/>
282  <meta content="630" property="og:image:height"/>
283  <meta content="summary_large_image" name="twitter:card"/>
284  <meta content="KJB Reader" name="twitter:title"/>
285  <meta content="Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word of God.,  " name="twitter:description"/>
286  <meta content="https://media.base44.com/images/public/6a8011c360ff52dad38eb2f3/e85b31b2e_10fb7857-d202-4070-98ce-c14094770136.jpg/v1/fill/w_1200,h_630/e85b31b2e_10fb7857-d202-4070-98ce-c14094770136.jpg" name="twitter:image"/>
287  <!-- PWA manifest: served by the dynamic `manifest` backend function so the
288         icons/screenshots saved from the DevTools Manifest editor take effect
289         live (it reads the ManifestConfig entity and responds no-store).
290         The URL is STILL a single stable path (required for Chromium install
291         eligibility) — it does NOT change per load, so the "Add to Home Screen"
292         / install prompt on Edge/Chrome mobile keeps working. -->
293  <link href="/functions/manifest" id="kjb-manifest-link" rel="manifest"/>
294  <!-- Prevent flash of wrong theme: apply saved theme BEFORE React mounts -->
295  
295<script>
296   (function() {
297        try {
298          // DIAGNOSTIC (temp, v20260904): dump origin + raw localStorage values
299          // at the earliest possible point (before any JS has a chance to touch
300          // them) so a cold offline launch tells us whether this is a wrong-
301          // origin/isolated-storage problem (all keys null, origin unexpected)
302          // or something narrower (only kjb-theme-mode missing). Remove once
303          // the offline-theme-reset bug is found.
304          console.log('[KJB-DEBUG] origin=' + location.origin + ' href=' + location.href);
305          console.log('[KJB-DEBUG] kjb-theme-mode=' + JSON.stringify(localStorage.getItem('kjb-theme-mode')) +
306            ' kjb-colour=' + JSON.stringify(localStorage.getItem('kjb-colour')) +
307            ' kjb-color-mode=' + JSON.stringify(localStorage.getItem('kjb-color-mode')) +
308            ' kjb-reader-font-family=' + JSON.stringify(localStorage.getItem('kjb-reader-font-family')) +
309            ' localStorage.length=' + localStorage.length);
310        } catch (e) { console.log('[KJB-DEBUG] error reading localStorage pre-mode: ' + e); }
311        try {
312          var mode = localStorage.getItem('kjb-theme-mode') || 'system';
313          var isDark;
314          if (mode === 'dark') isDark = true;
315          else if (mode === 'light') isDark = false;
316          else if (mode === 'auto') {
317            var h = new Date().getHours();
318            isDark = h < 6 || h >= 18;
319          } else {
320            isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
321          }
322          var rootEl = document.documentElement;
323          if (isDark) rootEl.classList.add('dark');
324          // Paint the correct canvas colour from the very first frame. The
325          // <style> block below does this too, but an inline style + the
326          // matching color-scheme is applied instantly and also tells the
327          // browser what to use for the pre-stylesheet blank page, which is
328          // what caused the white-then-dark flash on launch.
329          rootEl.style.colorScheme = isDark ? 'dark' : 'light';
330
331          // Apply the SAVED COLOUR PALETTE's surface tint before first paint.
332          // Without this, the first frame painted the default palette's canvas
333          // (and the fixed hex below), then visibly shifted once ThemeProvider
334          // applied the real palette after React mounted — the "portions still
335          // flash" problem. Values mirror applySurfaceTint() in
336          // src/lib/themeContext.jsx; keep the two in sync.
337          var KJB_HUES = { indigo: 245, sapphire: 222, sky: 200, teal: 174, forest: 142, amethyst: 271, rose: 347, crimson: 0, amber: 32, gold: 45, burgundy: 350, slate: 215, antique: 30 };
338          var colourId = localStorage.getItem('kjb-colour') || 'sky';
339          var colorMode = localStorage.getItem('kjb-color-mode') || 'fixed';
340          var hue = KJB_HUES[colourId];
341          if (colourId === 'custom') {
342            var hex = localStorage.getItem('kjb-custom-accent') || '#B8860B';
343            var r = parseInt(hex.substring(1, 3), 16) / 255,
344                g = parseInt(hex.substring(3, 5), 16) / 255,
345                b = parseInt(hex.substring(5, 7), 16) / 255;
346            var mx = Math.max(r, g, b), mn = Math.min(r, g, b), d = mx - mn, hh = 0;
347            if (d !== 0) {
348              if (mx === r) hh = ((g - b) / d + (g < b ? 6 : 0)) / 6;
349              else if (mx === g) hh = ((b - r) / d + 2) / 6;
350              else hh = ((r - g) / d + 4) / 6;
351            }
352            hue = Math.round(hh * 360);
353          }
354          var bgCss;
355          // 'daily' colour mode derives its accent from the day's verse at
356          // runtime, so only the fixed-palette case can be resolved this early.
357          if (typeof hue === 'number' && colorMode === 'fixed') {
358            var s = rootEl.style;
359            if (isDark) {
360              s.setProperty('--background', hue + ' 30% 8%');
361              s.setProperty('--foreground', hue + ' 25% 92%');
362              s.setProperty('--card', hue + ' 28% 12%');
363              s.setProperty('--card-foreground', hue + ' 25% 92%');
364              s.setProperty('--popover', hue + ' 28% 12%');
365              s.setProperty('--popover-foreground', hue + ' 25% 92%');
366              s.setProperty('--secondary', hue + ' 22% 18%');
367              s.setProperty('--secondary-foreground', hue + ' 25% 88%');
368              s.setProperty('--muted', hue + ' 22% 18%');
369              s.setProperty('--muted-foreground', hue + ' 15% 60%');
370              s.setProperty('--border', hue + ' 20% 22%');
371              s.setProperty('--input', hue + ' 20% 22%');
372              s.setProperty('--sidebar-background', hue + ' 30% 9%');
373              s.setProperty('--sidebar-foreground', hue + ' 25% 88%');
374              s.setProperty('--sidebar-accent', hue + ' 22% 18%');
375              s.setProperty('--sidebar-accent-foreground', hue + ' 25% 88%');
376              s.setProperty('--sidebar-border', hue + ' 20% 22%');
377              bgCss = 'hsl(' + hue + ' 30% 8%)';
378            } else {
379              s.setProperty('--background', hue + ' 30% 99%');
380              s.setProperty('--foreground', hue + ' 55% 8%');
381              s.setProperty('--card', hue + ' 12% 95%');
382              s.setProperty('--card-foreground', hue + ' 55% 8%');
383              s.setProperty('--popover', '0 0% 100%');
384              s.setProperty('--popover-foreground', hue + ' 55% 8%');
385              s.setProperty('--secondary', hue + ' 14% 89%');
386              s.setProperty('--secondary-foreground', hue + ' 30% 24%');
387              s.setProperty('--muted', hue + ' 14% 91%');
388              s.setProperty('--muted-foreground', hue + ' 12% 40%');
389              s.setProperty('--border', hue + ' 30% 89%');
390              s.setProperty('--input', hue + ' 30% 89%');
391              s.setProperty('--sidebar-background', hue + ' 40% 98%');
392              s.setProperty('--sidebar-foreground', hue + ' 35% 15%');
393              s.setProperty('--sidebar-accent', hue + ' 40% 95%');
394              s.setProperty('--sidebar-accent-foreground', hue + ' 45% 25%');
395              s.setProperty('--sidebar-border', hue + ' 30% 89%');
396              bgCss = 'hsl(' + hue + ' 30% 99%)';
397            }
398          } else {
399            bgCss = isDark ? '#0f1117' : '#fef9f3';
400          }
401          // The boot splash below paints this same colour so it matches too.
402          window.__kjbBootBg = bgCss;
403          rootEl.style.backgroundColor = bgCss;
404        } catch (e) {}
405        try {
406          // Apply the manual "App Zoom" setting BEFORE first paint, matching
407          // useBrowserZoom.js's manualFactor calc. Without this, the boot
408          // splash and first React paint render at 100% zoom, then visibly
409          // jump to the correct size once useBrowserZoom's effect runs.
410          var kjbZoomVal = parseInt(localStorage.getItem('kjb-layout-zoom') || '100', 10);
411          var kjbZoomFactor = (kjbZoomVal >= 50 && kjbZoomVal <= 200) ? kjbZoomVal / 100 : 1;
412          if (kjbZoomFactor !== 1) {
413            document.documentElement.style.setProperty('--kjb-zoom-scale', Str
413ing(kjbZoomFactor));
414            // Also set the root font-size directly (not just the CSS variable) —
415            // the variable only takes effect once index.css's `:root { font-size:
416            // calc(...) }` rule loads, which can lag behind this inline script.
417            // Without this, the rem-sized boot splash below paints at the
418            // default 100% size first, then visibly jumps to the saved zoom
419            // once the stylesheet arrives. Setting it here makes the splash
420            // correctly sized from the very first frame, every reopen.
421            document.documentElement.style.fontSize = (kjbZoomFactor * 100) + '%';
422          }
423        } catch (e) {}
424        try {
425          // A shared link can carry ?font=dyslexic or ?font=hyperlegible —
426          // persist it so the recipient opens (and stays) in that font mode.
427          var qsFont = window.location.search || '';
428          if (qsFont.indexOf('font=dyslexic') !== -1) {
429            localStorage.setItem('kjb-a11y-font', 'dyslexic');
430          } else if (qsFont.indexOf('font=hyperlegible') !== -1) {
431            localStorage.setItem('kjb-a11y-font', 'hyperlegible');
432          }
433          var a11yFont = localStorage.getItem('kjb-a11y-font');
434          if (a11yFont === 'dyslexic' || a11yFont === 'hyperlegible' || a11yFont === 'system') {
435            document.documentElement.setAttribute('data-a11y-font', a11yFont);
436          }
437        } catch (e) {}
438      })();
439  </script>
439
440  <style>
441   /* Boot background matches the splash placeholder — light in light mode,
442         dark in dark mode — to prevent a flash of the wrong color before React
443         mounts. The inline theme script adds `html.dark` for dark mode. */
444      html, body { background-color: #fef9f3; color: #0f1718; margin: 0; width: 100%; min-height: 100vh; }
445      html.dark, html.dark body { background-color: #0f1117; color: #ffffff; }
446      #root { min-height: 100vh; width: 100%; background-color: inherit; }
447  </style>
448  
448<script crossorigin="" src="/assets/index-DwOsqt_4.js" type="module">
449  </script>
449
450  <link crossorigin="" href="/assets/index-BqHR-vAv.css" rel="stylesheet"/>
451  
451<script type="module">
452   if (window.self === window.top) {
453  let lastPath = "";
454  function getPageNameFromPath(path) {
455    const segments = path.split("/").filter(Boolean);
456    return segments[0] || null;
457  }
458  function trackPageView() {
459    const path = window.location.pathname;
460    if (path === lastPath) return;
461    lastPath = path;
462    const pageName = getPageNameFromPath(path) || "home";
463    const appId = "6a8011c360ff52dad38eb2f3";
464    if (!appId) return;
465    fetch(`/api/app-logs/${appId}/log-user-in-app/${pageName}`, {
466      method: "POST",
467    }).catch(() => {});
468  }
469  const originalPushState = history.pushState.bind(history);
470  history.pushState = function (...args) {
471    originalPushState(...args);
472    trackPageView();
473  };
474  const originalReplaceState = history.replaceState.bind(history);
475  history.replaceState = function (...args) {
476    originalReplaceState(...args);
477    trackPageView();
478  };
479  window.addEventListener("popstate", trackPageView);
480  trackPageView();
481}
482  </script>
482
483  <meta content="https://kingjamesbiblereader.com" property="og:url"/>
484  <meta content="website" property="og:type"/>
485  <meta content="KJB Reader" property="og:site_name"/>
486  <meta content="https://kingjamesbiblereader.com" name="twitter:url"/>
487  
487<script>
488   (function () {
489  var titles = {"/login": "Login | KJB Reader", "/forgot-password": "Forgot Password | KJB Reader", "/reset-password": "Reset Password | KJB Reader", "/register": "Register | KJB Reader", "/oauth/consent": "O Auth Consent | KJB Reader", "/credits": "Credits Page | KJB Reader", "/changelog": "Changelog Page | KJB Reader", "/extension/change-log": "Changelog Page | KJB Reader", "/terms": "Terms Of Service Page | KJB Reader", "/privacy": "Privacy Policy Page | KJB Reader", "/contact": "Contact Page | KJB Reader", "/salvation": "Salvation Page | KJB Reader", "/discord": "Discord Invite Page | KJB Reader", "/extension": "Extension Page | KJB Reader", "/extension-privacy": "Extension Privacy Page | KJB Reader", "/extension/privacy": "Extension Privacy Page | KJB Reader", "/extension-terms": "Extension Terms Page | KJB Reader", "/extension-license": "Extension License Page | KJB Reader", "/landing": "Landing Page | KJB Reader", "/espanol-evangelio": "Spanish Gospel Page | KJB Reader", "/": "KJB Reader", "/read": "Bible Reader | KJB Reader", "/gospel": "Gospel Page | KJB Reader", "/resources": "Resources Page | KJB Reader", "/kjb-defence": "Kjb Defence Page | KJB Reader", "/about": "About Page | KJB Reader", "/contents": "Contents Page | KJB Reader", "/settings": "Settings Page | KJB Reader", "/search": "Search Page | KJB Reader", "/advanced-search": "Advanced Search Page | KJB Reader", "/saved": "Saved Verses Page | KJB Reader", "/refresh-cache": "Refresh Cache | KJB Reader", "/manifest-icons": "Manifest Icons | KJB Reader", "/manifest-screenshots": "Manifest Screenshots | KJB Reader", "/legacy": "Legacy Reader | KJB Reader", "/dev-tools": "Dev Tools Page | KJB Reader", "/espanol": "Spanish Resources Page | KJB Reader", "/bible.txt": "Bible Txt | KJB Reader", "/home-page": "KJB Reader", "/homepage": "KJB Reader"};
490  // PascalCase boundaries -> "-", mirroring _PASCAL_BOUNDARY_RE without lookbehind
491  // (old Safari fails to PARSE a lookbehind, taking the whole script down).
492  var dashed = function (s) {
493    return s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2");
494  };
495  var decode = function (s) {
496    // A %2F inside ONE segment must not become a structural separator: /blog/foo%2Fbar is a
497    // dynamic slug, and letting it split would match a static /blog/foo/bar and write ITS title.
498    try { var d = decodeURIComponent(s); return d.indexOf("/") < 0 ? d : s; } catch (e) { return s; }
499  };
500  var segments = function (path) { return path.split("/").filter(Boolean).map(decode); };
501  var norm = function (path) {
502    var parts = segments(path).map(function (s) { return dashed(s).toLowerCase(); });
503    return parts.length ? "/" + parts.join("/") : "/";
504  };
505  var lastPath = location.pathname, written = document.title, on = true;
506  var sync = function () {
507    // Path only: a hash/query change is the same page, and the SDK strips its own auth
508    // params with replaceState -- neither must clobber a server-rendered title in place.
509    if (!on || location.pathname === lastPath) return;
510    lastPath = location.pathname;
511    if (document.title !== written) { on = false; return; }
512    // Write ONLY a title the map proves. An unmapped path is a dynamic route (/blog/:slug) the
513    // map cannot carry, and writing a placeholder there is destructive, not merely vague: the
514    // prerender worker runs no setUserAgent, so its capture browser reads as human and executes
515    // this script, and coordinator.js's settle wait explicitly tolerates a full navigation --
516    // a redirect landing on an unmapped path would bake the placeholder into the stored
517    // snapshot. Leaving the title alone degrades to today's behavior instead.
518    // Exact lookup only. A dash-stripped retry here would let a genuinely different /set-up
519    // claim /setup: the map carries a dash-free key only where a PascalCase boundary put the
520    // dashes there, and the client cannot tell the two apart. The map emits every reachable
521    // shape per segment instead (_client_url_variants).
522    var title = titles[norm(location.pathname)];
523    if (title === undefined) return;
524    document.title = title;
525    // Re-read: the setter stores verbatim but the getter strips and collapses whitespace, so
526    // keeping the assigned string would make the next sync() read our own write as the app's.
527    written = document.title;
528  };
529  var wrap = function (fn) {
530    // The history entry is already pushed by the time sync() runs, so a throw here would
531    // surface inside the host app's router and break navigation. This is injected into apps
532    // we cannot test, so it swallows rather than trusting the trace.
533    return function () {
534      var r = fn.apply(this, arguments);
535      try { sync(); } catch (e) {}
536      return r;
537    };
538  };
539  history.pushState = wrap(history.pushState);
540  history.replaceState = wrap(history.replaceState);
541  window.addEventListener("popstate", sync);
542})();
543  </script>
543
544  <link href="https://kingjamesbiblereader.com" rel="canonical"/>
545  
545<script data-seo-source="builder" type="application/ld+json">
546   {"name": "KJB Reader", "@context": "https://schema.org", "@type": "WebSite", "url": "https://kingjamesbiblereader.com"}
547  </script>
547
548  
548<script data-seo-source="builder" type="application/ld+json">
549   {"name": "KJB Reader", "logo": "https://media.base44.com/images/public/6a8011c360ff52dad38eb2f3/03be03ba6_8e738d108_cfb4bf781_Untitled.png", "@context": "https://schema.org", "@type": "Organization", "url": "https://kingjamesbiblereader.com"}
550  </script>
550
551 </head>
552 <body>
553  <div id="root">
554   <div data-seo-source="builder" id="seo-snapshot" style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;">
555    <main>
556     <header>
557      <h1>
558       KJB Reader
559      </h1>
560      <p>
561       Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word of God.,
562      </p>
563     </header>
564     <nav aria-label="Pages">
565      <h2>
566       Pages
567      </h2>
568      <ul>
569       <li>
570        <a href="/credits">
571         Credits Page
572        </a>
573        — Credits for the sources and functions that are used for this website to work.
574       </li>
575       <li>
576        <a href="/changelog">
577         Changelog Page
578        </a>
579        — KJB Reader - SidePanel extension change-logs.
580       </li>
581       <li>
582        <a href="/terms">
583         Terms Of Service Page
584        </a>
585        — Terms Of Service Page for KJB Reader Website.
586       </li>
587       <li>
588        <a href="/privacy">
589         Privacy Policy Page
590        </a>
591        — Privacy Policy for KJB Reader Website.
592       </li>
593       <li>
594        <a href="/contact">
595         Contact Page
596        </a>
597        — Contact me for feedback or issues on the website.
598       </li>
599       <li>
600        <a href="/salvation">
601         Salvation Page
602        </a>
603        — Salvation Page on KJB Reader. Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word.
604       </li>
605       <li>
606        <a href="/discord">
607         Discord Invite Page
608        </a>
609        — Discord links to download the KJB Reader Bot and more.
610       </li>
611       <li>
612        <a href="/extension">
613         Extension Page
614        </a>
615        — Get the KJB Reader - SidePanel extension for Edge, Chrome, FireFox, Brave and Opera!
616       </li>
617       <li>
618        <a href="/extension-privacy">
619         Extension Privacy Page
620        </a>
621        — Privacy Policy for KJB Reader - SidePanel Extension,
622       </li>
623       <li>
624        <a href="/extension-terms">
625         Extension Terms Page
626        </a>
627        — Extension Terms Page on KJB Reader. Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word.
628       </li>
629       <li>
630        <a href="/extension-license">
631         Extension License Page
632        </a>
633        — License policy for KJB Reader - SidePanel Extension.
634       </li>
635       <li>
636        <a href="/espanol-evangelio">
637         Spanish Gospel Page
638        </a>
639        — Spanish Gospel Page on KJB Reader. Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word.
640       </li>
641       <li>
642        <a href="/read">
643         Bible Reader
644        </a>
645        — Bible Reader on KJB Reader. Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word.
646       </li>
647       <li>
648        <a href="/gospel">
649         Gospel Page
650        </a>
651        — Learn about how to be saved from the damnation to come.
652       </li>
653       <li>
654        <a href="/resources">
655         Resources Page
656        </a>
657        — Resources Page on KJB Reader. Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word.
658       </li>
659       <li>
660        <a href="/kjb-defence">
661         Kjb Defence Page
662        </a>
663        — KJB Defence Page with many resources.
664       </li>
665       <li>
666        <a href="/about">
667         About Page
668        </a>
669        — About KJB Reader.
670       </li>
671       <li>
672        <a href="/contents">
673         Contents Page
674        </a>
675        — Search through the Table of Contents for the King James Bible.
676       </li>
677       <li>
678        <a href="/settings">
679         Settings Page
680        </a>
681        — Adjust your KJB Reader App settings.
682       </li>
683       <li>
684        <a href="/search">
685         Search Page
686        </a>
687        — Search through the King James Bible.
688       </li>
689       <li>
690        <a href="/advanced-search">
691         Advanced Search Page
692        </a>
693        — Search through the KJB with advanced filters.
694       </li>
695       <li>
696        <a href="/saved">
697         Saved Verses Page
698        </a>
699        — Find the verses you saved in the app.
700       </li>
701       <li>
702        <a href="/legacy">
703         Legacy Reader
704        </a>
705        — Read the KJB on Legacy browsers via an downloadable html file. 
706
707Tested on Internet Explorer 11 on Windows 8.1.
708       </li>
709       <li>
710        <a href="/espanol">
711         Spanish Resources Page
712        </a>
713        — Spanish Resources Page on KJB Reader. Read the King James Bible (Pure Cambridge Edition) — the pure, infallible Word.
714       </li>
715      </ul>
716     </nav>
717    </main>
718   </div>
719  </div>
720  <!-- Static placeholder that EXACTLY matches the React SplashScreen's idle
721           look (same logo size, progress bar, uppercase text) so there is no
722           visual flash/jump when React mounts and takes over. Colors are theme
723           aware: dark bg in dark mode, light bg in light mode. The inline theme
724           script above adds the `dark` class before this renders, so we read it
725           and set the splash colors here before paint. -->
726  <div id="kjb-boot-splash" style="position:fixed;inset:0;z-index:999999;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#0f1117;opacity:0;transition:opacity .2s ease;">
727   <div style="display:flex;flex-direction:column;align-items:center;margin-top:-4rem;gap:48px;">
728    <img alt="KJB Reader Logo" id="kjb-boot-logo" style="width:11rem;height:11rem;object-fit:contain;border-radius:1rem;padding:0.75rem;background:#0f1117;"/>
729    <div style="display:flex;flex-direction:column;align-items:center;gap:20px;width:16rem;">
730     <div id="kjb-boot-track" style="width:100%;height:6px;border-radius:9999px;overflow:hidden;position:relative;background:rgba(255,255,255,0.08);">
731      <div style="position:absolute;top:0;height:100%;border-radius:9999px;width:40%;background:#4f6aff;animation:kjb-boot-indeterminate 1.2s ease-in-out infinite;">
732      </div>
733     </div>
734     <span id="kjb-boot-text" style="font-family:Inter,system-ui,sans-serif;font-size:0.875rem;font-weight:300;letter-spacing:0.25em;text-transform:uppercase;text-align:center;color:#c8cdd8;">
735      Welcome to KJB Reader.
736     </span>
737    </div>
738   </div>
739  </div>
740  
740<script>
741   (function () {
742          try {
743            // If this fresh page load is landing directly on /search --
744            // i.e. a "Look Up" from another app, or a shared/bookmarked
745            // search link -- "Welcome to KJB Reader." doesn't fit; match
746            // the React SplashScreen's own isLookup logic (App.jsx /
747            // SplashScreen.jsx) so this static PRE-React placeholder shows
748            // the same "LOOKING UP..." wording instead. Without this, the
749            // static placeholder (visible for a brief moment before React
750            // mounts and takes over) showed the generic welcome text, then
751            // visibly swapped to "LOOKING UP..." once React's own splash
752            // rendered -- a small but noticeable mismatched flash.
753            var bootText = document.getElementById('kjb-boot-text');
754            // DIAGNOSTIC: gated behind Settings > App Info > "Startup
755            // Diagnostics" (kjb-debug-diagnostics). Off by default -- a
756            // small tappable button instead of a full-width box, since the
757            // box was covering the reader/splash on small screens. Tapping
758            // copies the diagnostic text to the clipboard rather than
759            // displaying it all on screen.
760            try {
761              if (localStorage.getItem('kjb-debug-diagnostics') === 'true') {
762                var buildDbgText = function(tag) {
763                  var carryParam = (location.search || '').indexOf('__kjb_carry=') !== -1;
764                  return '[' + tag + '] origin=' + location.origin +
765                    ' | hasCarryParam=' + carryParam +
766                    ' | search=' + location.search +
767                    ' | mode=' + JSON.stringify(localStorage.getItem('kjb-theme-mode')) +
768                    ' | colour=' + JSON.stringify(localStorage.getItem('kjb-colour')) +
769                    ' | color-mode=' + JSON.stringify(localStorage.getItem('kjb-color-mode')) +
770                    ' | font=' + JSON.stringify(localStorage.getItem('kjb-reader-font-family')) +
771                    ' | hasVisited=' + JSON.stringify(localStorage.getItem('kjb-has-visited-app')) +
772                    ' | mirror=' + JSON.stringify(localStorage.getItem('kjb-mirror-status')) +
773                    ' | ls.length=' + localStorage.length +
774                    ' | online=' + navigator.onLine;
775                };
776                var dbgLines = [buildDbgText('pre-main')];
777                // Pre-main.jsx is EXPECTED to show ls.length=0 even when the
778                // carry param is present -- main.jsx hasn't decoded/applied
779                // it yet at this point in the file. The post-mount line
780                // 800ms later is the one that actually proves whether the
781                // carry applied in time.
782                setTimeout(function () { dbgLines.push(buildDbgText('post-main+800ms')); }, 800);
783
784                var fallbackCopy = function (text) {
785                  try {
786                    var ta = document.createElement('textarea');
787                    ta.value = text;
788                    ta.style.position = 'fixed';
789                    ta.style.opacity = '0';
790                    document.body.appendChild(ta);
791                    ta.focus();
792                    ta.select();
793                    document.execCommand('copy');
794                    document.body.removeChild(ta);
795                  } catch (e) {}
796                };
797
798                var dbgBtn = document.createElement('button');
799                dbgBtn.id = 'kjb-debug-btn';
800                dbgBtn.textContent = 'DBG';
801                dbgBtn.style.cssText = 'position:fixed;right:10px;bottom:10px;z-index:9999999;background:rgba(0,0,0,0.75);color:#0f0;font-family:monospace;font-size:11px;font-weight:bold;padding:8px 12px;border-radius:20px;border:1px solid #0f0;opacity:0.85;';
802                dbgBtn.addEventListener('click', function () {
803                  var text = dbgLines.join('\n');
804                  var showCopied = function () {
805                    dbgBtn.textContent = 'Copied!';
806                    setTimeout(function () { dbgBtn.textContent = 'DBG'; }, 1200);
807                  };
808                  if (navigator.clipboard && navigator.clipboard.writeText) {
809                    navigator.clipboard.writeText(text).then(showCopied).catch(function () {
810                      fallbackCopy(text);
811                      showCopied();
812                    });
813                  } else {
814                    fallbackCopy(text);
815                    showCopied();
816                  }
817                });
818                document.body.appendChild(dbgBtn);
819              }
820            } catch (e) {}
821            if (bootText) {
822              var hasVisited = localStorage.getItem('kjb-has-visited-app') === 'true';
823              // Must match SplashScreen.jsx's lookupSplashLabel() exactly (the
824              // CSS uppercases this) so a lookup shows ONE label, not
825              // "Looking up..." swapping to "Looking up "Romans 3:25"...".
826              var lookupQ = '';
827              try { lookupQ = (new URLSearchParams(location.search).get('q') || '').trim(); } catch (e) {}
828              bootText.textContent = (location.pathname === '/search')
829                ? (lookupQ ? 'Looking up \u201C' + lookupQ + '\u201D\u2026' : 'Looking up\u2026')
830                : (hasVisited ? 'Welcome back to KJB Reader.' : 'Welcome to KJB Reader.');
831            }
832            // Use the base64-cached logo from localStorage if available (works
833            // offline without SW). Falls back to the cross-origin URL online.
834            var logoEl = document.getElementById('kjb-boot-logo');
835            var cachedLogo = localStorage.getItem('kjb-splash-logo-dataurl');
836            logoEl.src = cachedLogo ||
837              // On the native offline-fallback copy (capacitor: origin) the logo
838              // comes from the app bundle, so it shows with no connection.
839              (window.location && window.location.protocol === 'capacitor:'
840                ? '/__native/logo.png'
841                : 'https://media.base44.com/images/public/6a05d76723afe58d80c589e8/2279e016e_8e738d108_cfb4bf781_Untitled.png');
842            // Paint the splash in the theme's own resolved canvas colour
843            // (set by the theme script above), so there's no colour shift
844            // between the splash and the app underneath it.
845            var bg = window.__kjbBootBg || '#0f1117';
846            document.getElementById('kjb-boot-splash').style.background = bg;
847            logoEl.style.background = bg;
848            if (!document.documentElement.classList.contains('dark')) {
849              document.getElementById('kjb-boot-track').style.background = 'rgba(0,0,0,0.08)';
850              document.getElementById('kjb-boot-text').style.color = '#5a6472';
851            }
852            // Installed desktop PWAs open at a temporary default window size
853            // that Chrome/Edge resizes to the remembered size almost immediately
854            // after launch. If the splash paints before that resize settles, the
855            // logo/progress bar appear to "shrink" as the window snaps to size —
856            // that's the browser's window, not this content, changing. Hide the
857            // splash for a beat in standalone mode so it only becomes visible
858            // once the window has settled, and reveal instantly everywhere else
859            // (plain browser tabs aren't affected and shouldn't see extra delay).
860            //
861            // Native Android (Capacitor) is excluded from this delay entirely:
862            // it ALSO matches display-mode: standalone, so it was getting this
863            // same 150ms treatment despite the window-resize issue it exists
864            // for being purely a desktop-browser-chrome concern -- a native
865            // Activity is already a fixed, full-screen surface with nothing to
866            // "settle". During that unnecessary delay, this placeholder stayed
867            // invisible while React was often already bootstrapping underneath
868            // (e.g. a "Look Up" launch straight to /search), so whatever React
869            // had rendered so far -- typically the lazy-route Suspense
870            // fallback's spinner -- was briefly visible unmasked, before this
871            // placeholder (and shortly after, the real SplashScreen) finally
872            // appeared. window.androidBridge is Capacitor's own native JS
873            // interface, injected directly by MainActivity.java and
874            // synchronously available the instant any page JS runs in that
875            // WebView -- reliable regardless of online/offline state, unlike
876            // Capacitor's own bridge script injection (see MainActivity.java's
877            // injectNativeMarker()).
878            var boot = document.getElementById('kjb-boot-splash');
879            var isNativeAndroid = !!window.androidBridge;
880            var isStandalone = !isNativeAndroid && ((window.matchMedia && (
881              window.matchMedia('(display-mode: standalone)').matches ||
882              window.matchMedia('(display-mode: window-controls-overlay)').matches ||
883              window.matchMedia('(display-mode: minimal-ui)').matches
884            )) || navigator.standalone === true);
885            if (isStandalone) {
886              setTimeout(function () { boot.style.opacity = '1'; }, 150);
887            } else {
888              boot.style.opacity = '1';
889            }
890          } catch (e) {
891            var fallback = document.getElementById('kjb-boot-splash');
892            if (fallback) fallback.style.opacity = '1';
893          }
894        })();
895  </script>
895
896  <style>
897   @keyframes kjb-boot-indeterminate {
898          0% { left: -40%; }
899          100% { left: 100%; }
900        }
901  </style>
902 </body>
903</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.