JustToThePoint English Website Version
JustToThePoint en español

Bible Service. Offline KJV Lookup

The first principle is that you must not fool yourself – and you are the easiest person to fool, Richard Feynman

Behind this mask there is more than just flesh. Beneath this mask there is an idea… and ideas are bulletproof, Alan Moore

Fourier Series

What you will learn

What you will build

`bibleService.ts` will expose three things:

| Export | Description |
|--------|-------------|
| `BOOK_MAP` | Maps full book names (`"Genesis"`, `"John"`) to the abbreviations used by freebible (`"Gen"`, `"John"`) and vice versa. |
| `parseReference(ref)` | Parses a human‑readable string like `"John 3:16"` → `{ book: "John", chapter: 3, verse: 16 } \| null`. |
| `lookupKJV(book, chapter, verse)` | Returns the verse text directly from the bundled JSON — **offline**, with zero latency. |

Step 1 — Generate data/kjv.json from freebible

We suppose that you already have freebible installed in your Python venv. We are taking the KJV text stored inside the freebible Python package and exporting it into a single JSON file: data/kjv.json

# 1. Activate your Python venv (the one with freebible installed)
source /mnt/data/justtothepoint/mypython/venv/bin/activate

# 2. Run an inline Python script. Generate data/kjv.json
# This is a here-document: you embed Python code directly inside the shell script.
python3 - << 'GENSCRIPT'
"""
Generate data/kjv.json from the freebible Python package.

This JSON file will contain the entire King James Bible in a clean, lookup‑friendly structure:
  {
    "Gen":  { "1": { "1": "In the beginning God created...", "2": "..." } },
    "John": { "3": { "16": "For God so loved the world..." } },
    ...
  }

Keys at every level are STRINGS, not numbers, because JSON object keys
are always strings and we want consistent lookup behaviour.

File size:
  The full KJV (31,102 verses) produces a file of approximately 3–4 MB.
  Metro bundles it at build time; require() inlines it as a JS object.
  Lookup is instant (no async fetch).
"""

import json
import sys
from pathlib import Path

try:
    # 3. Import freebible
    # bibles.web.quote(book, chapter, verse) is the key function.
    # It returns strings like: "John 3:16 For God so loved the world..."
    from freebible import bibles
except ImportError:
    print("ERROR: freebible not installed. Run: pip install freebible")
    sys.exit(1)

print("Building KJV JSON from freebible...")

# 4. Prepare the output structure.
# This will become:
# {
#   "Gen": { "1": { "1": "...", "2": "..." } },
#   "Exo": { ... },
#  ...
# }
kjv: dict = {}
verse_count = 0
error_count = 0

# 5. Define the book abbreviations
# These are the exact keys that freebible expects.
# If you pass "Genesis" instead of "Gen", it will fail.
BOOK_ABBREVS = [
    "Gen","Exo","Lev","Num","Deut","Josh","Judg","Rth",
    "1 Sam","2 Sam","1 Kgs","2 Kgs","1 Chron","2 Chron",
    "Ezra","Neh","Esth","Job","Pslm","Prov","Eccles","Song",
    "Isa","Jer","Lam","Ezek","Dan","Hos","Joel","Amos",
    "Obad","Jnh","Micah","Nah","Hab","Zeph","Haggai","Zech","Mal",
    "Matt","Mrk","Luk","John","Acts","Rom",
    "1 Cor","2 Cor","Gal","Ephes","Phil","Col",
    "1 Thess","2 Thess","1 Tim","2 Tim","Titus","Philem",
    "Hebrews","James","1 Pet","2 Pet","1 John","2 John","3 John",
    "Jude","Rev",
]

# 6. Loop through every book, chapter, and verse.
MAX_CHAPTERS = 150
MAX_VERSES   = 176   # longest chapter in the Bible is Psalm 119 (176 verses)

# Book loop
for book in BOOK_ABBREVS:
    kjv[book] = {}
    # Chapter loop
    # We don’t know how many chapters each book has, so we try up to 150 and stop when no verses are found.
    for chapter in range(1, MAX_CHAPTERS + 1):
        chapter_data = {}
        # Verse loop
        # Same idea: try up to 176 verses (Psalm 119 is the longest chapter in the Bible).
        for verse in range(1, MAX_VERSES + 1):
            try:
                # 7. Extract each verse
                result = bibles.web.quote(book, chapter, verse)
                text = str(result).strip()
                # freebible returns "book chapter:verse text"
                # Then, we need to remove the "book chapter:verse " prefix:
                # Format: "John 3:16 For God so loved..."
                prefix = f"{book} {chapter}:{verse} "
                if text.startswith(prefix):
                    text = text[len(prefix):].strip()

                # If the verse exists, store it:
                if text and text != "None":
                    chapter_data[str(verse)] = text
                    verse_count += 1
                # If not, break out of the verse loop (no more verses in this chapter).
                else:
                    break
            except Exception:
                break       # No more verses in this chapter
        if chapter_data:
            kjv[book][str(chapter)] = chapter_data
        else:
            break           # No more chapters in this book

output_path = Path("data/kjv.json")
output_path.parent.mkdir(exist_ok=True)

# 8. Write the JSON file
with open(output_path, "w", encoding="utf-8") as f:
    # separators=(",", ":") removes all spaces — smallest possible file
    json.dump(kjv, f, ensure_ascii=False, separators=(",", ":"))

# 9. Print summary
size_mb = output_path.stat().st_size / 1_048_576
print(f"✅ Generated data/kjv.json")
print(f"   Books:  {len(kjv)}")
print(f"   Verses: {verse_count:,}")
print(f"   Size:   {size_mb:.1f} MB")
GENSCRIPT

Expected output:

Building KJV JSON from freebible...
✅ Generated data/kjv.json
   Books:  66
   Verses: 31,102
   Size:   3.4 MB

Step 2 — Verify the JSON structure

Before we write any TypeScript code that depends on the generated file, we must be absolutely sure (i) the file exists; (ii) is the right size, (iii) and has the triply‑nested shape we expect.

# 1. Check file existence and size — should be roughly 3.4 MB
ls -lh data/kjv.json

# 2. Inspect the internal structure and spot‑check three well‑known verses
python3 -c "
import json
kjv = json.load(open('data/kjv.json'))
print('Books:', len(kjv))
print('John 3:16:', kjv['John']['3']['16'])
print('Genesis 1:1:', kjv['Gen']['1']['1'])
print('Psalm 23:1:', kjv['Pslm']['23']['1'])
"

Expected output:


Books: 66
John 3:16: For God so loved the world, that he gave his only begotten Son...
Genesis 1:1: In the beginning God created the heaven and the earth.
Psalm 23:1: The LORD is my shepherd; I shall not want.

Step 3 — Append the offline lookup logic to src/services/bibleService.ts

Open src/services/bibleService.ts and append the following code at the end of the file. Do not delete any existing content.

// ──────────────────────────────────────────────────────────────────────────────
// OFFLINE KJV LOOKUP
// ──────────────────────────────────────────────────────────────────────────────


// ══════════════════════════════════════════════════════════════════════════════
// BOOK MAP
// Translates full book names (as written in daily_plan.txt) to the
// abbreviations used as keys in kjv.json and by freebible.
// Why this map matters? **User‑friendly input** – Our daily plan, UI, and parseReference function
// will all work with familiar names like "1 Samuel" or "Psalms". This map
// converts them to the compact abbreviations that kjv.json actually uses as
// keys (e.g. "1 Sam", "Pslm").
// **Unambiguous bridging** – The freebible package uses slightly non‑standard
// abbreviations (e.g. "Pslm" for Psalms, "Jnh" for Jonah). Rather than
// forcing users to learn these, we keep the complexity hidden behind a single
// constant.
// ══════════════════════════════════════════════════════════════════════════════

export const BOOK_MAP: Record<string, string> = {
  // Pentateuch
  "Genesis":          "Gen",
  "Exodus":           "Exo",
  "Leviticus":        "Lev",
  "Numbers":          "Num",
  "Deuteronomy":      "Deut",
  // History
  "Joshua":           "Josh",
  "Judges":           "Judg",
  "Ruth":             "Rth",
  "1 Samuel":         "1 Sam",
  "2 Samuel":         "2 Sam",
  "1 Kings":          "1 Kgs",
  "2 Kings":          "2 Kgs",
  "1 Chronicles":     "1 Chron",
  "2 Chronicles":     "2 Chron",
  "Ezra":             "Ezra",
  "Nehemiah":         "Neh",
  "Esther":           "Esth",
  // Wisdom
  "Job":              "Job",
  "Psalms":           "Pslm",
  "Psalm":            "Pslm",    // both "Psalms" and "Psalm" are common
  "Proverbs":         "Prov",
  "Ecclesiastes":     "Eccles",
  "Song of Solomon":  "Song",
  // Major Prophets
  "Isaiah":           "Isa",
  "Jeremiah":         "Jer",
  "Lamentations":     "Lam",
  "Ezekiel":          "Ezek",
  "Daniel":           "Dan",
  // Minor Prophets
  "Hosea":            "Hos",
  "Joel":             "Joel",
  "Amos":             "Amos",
  "Obadiah":          "Obad",
  "Jonah":            "Jnh",
  "Micah":            "Micah",
  "Nahum":            "Nah",
  "Habakkuk":         "Hab",
  "Zephaniah":        "Zeph",
  "Haggai":           "Haggai",
  "Zechariah":        "Zech",
  "Malachi":          "Mal",
  // Gospels
  "Matthew":          "Matt",
  "Mark":             "Mrk",
  "Luke":             "Luk",
  "John":             "John",
  // Acts
  "Acts":             "Acts",
  // Paul
  "Romans":           "Rom",
  "1 Corinthians":    "1 Cor",
  "2 Corinthians":    "2 Cor",
  "Galatians":        "Gal",
  "Ephesians":        "Ephes",
  "Philippians":      "Phil",
  "Colossians":       "Col",
  "1 Thessalonians":  "1 Thess",
  "2 Thessalonians":  "2 Thess",
  "1 Timothy":        "1 Tim",
  "2 Timothy":        "2 Tim",
  "Titus":            "Titus",
  "Philemon":         "Philem",
  // General Epistles
  "Hebrews":          "Hebrews",
  "James":            "James",
  "1 Peter":          "1 Pet",
  "2 Peter":          "2 Pet",
  "1 John":           "1 John",
  "2 John":           "2 John",
  "3 John":           "3 John",
  "Jude":             "Jude",
  // Revelation
  "Revelation":       "Rev",
};

// ══════════════════════════════════════════════════════════════════════════════
// Lazy load the KJV data
//
// Why not just require('./kjv.json') at the top of the file?
// Because the module is imported by bibleService.ts which is imported by
// the app entry point. Forcing the 3.4 MB JSON to be parsed during module
// initialisation would add 20‑50 ms to every cold start — even when the user
// never looks up a verse. That parsing happens before your app renders anything!!!
//
// Instead we load it on the FIRST call to lookupKJV() and cache the result.
// Subsequent calls are instant (the object is already in memory).
//
// ══════════════════════════════════════════════════════════════════════════════

/**
 * {
  "John": {
    "3": {
      "16": "For God so loved the world..."
    }
  },
  "Gen": {
    "1": {
      "1": "In the beginning God created..."
    }
  }
 }
 * The triply‑nested data structure:
 *   kjv["John"]["3"]["16"] = "For God so loved the world..."
 *   kjv["Gen"]["1"]["1"] = "In the beginning God created…"
 *   Level 1 — Book: "John", "Gen", "Ps", "Rom", etc. TypeScript Representation: Record<string, ...>
 *   Level 2 — Chapter: "1", "2", "3", … "150". TypeScript Representation: Record<string, ...>
 *   Level 3 — Verse: "1", "2", "16", … TypeScript Representation: Record<string, string>
 *   Level 4 — Text. A string containing the verse. TypeScript Representation: string
 */
type KJVData = Record<string, Record<string, Record<string, string>>>;

// Singleton cache — null means "not loaded first" until first lookup, then populated forever
let _kjvCache: KJVData | null = null;

/**
 * Instead of loading the JSON at module import time, we load it inside this function,
 * only when needed:
 *
 * Why require() instead of expo-file-system?
 * Metro, the React Native bundler, sees require('../../data/kjv.json') at
 * build time. It reads the JSON file, parses it into a JS object,
 * embeds that object directly into the bundle. At runtime, require() simply returns that object instantly.
 * (i) No file I/O, no async.
 * (ii) No JSON.parse. The object is immediately available — no await needed.
 * (iii) No failure unless the file is missing
 *
 * @returns The full KJV data object, or null if the file is missing or cannot be loaded.
 */
function getKJVData(): KJVData | null {
  // 1. Return cached object if already loaded
  if (_kjvCache !== null) {
    return _kjvCache;
  }

  try {
    // 2. Require the JSON file.
    //    Metro inlines the parsed object at build time – no runtime I/O.
    //    We disable the eslint rule because require() is used intentionally.
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const raw: unknown = require("../../data/kjv.json");

    // 3. Defensive checks – the file might be missing or corrupted.
    // !raw catches null, undefined, false, or empty value
    // typeof raw !== "object". Valid JSON objects load as: typeof raw === "object".
    //  Array.isArray(raw) reject any arrays (it protects from accepting a wrong shape).
    if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
      console.error("[Bible] kjv.json did not load as an object.");
      return null;
    }

    // 4. Handle the ESM `default` wrapper that some bundlers add.
    // {
    //    "default": { ...actual data... }
    // }
    // If the top‑level object has a key called "default" and no book keys,
    //  we unwrap it. This is the same issue seen with seed_prayers.json.
    const data = raw as Record<string, unknown>;
    const kjv = "default" in data
      ? (data.default as KJVData)
      : (raw as KJVData);

    _kjvCache = kjv;
    console.log(`[Bible] KJV loaded — ${Object.keys(kjv).length} books`);
    return _kjvCache;

  } catch (e) {
    console.error("[Bible] Failed to load kjv.json:", e);
    return null;
  }
}

// ══════════════════════════════════════════════════════════════════════════════
// REFERENCE PARSER
// ══════════════════════════════════════════════════════════════════════════════

/**
 * Parse a human-readable Bible reference into its components.
 *
 * Supported formats:
 *   "John 3:16"          → { bookAbbrev: "John",  chapter: 3,  verses: [16] }
 *   "1 Peter 5:7"        → { bookAbbrev: "1 Pet", chapter: 5,  verses: [7]  }
 *   "Psalm 23:1-3"       → { bookAbbrev: "Pslm",  chapter: 23, verses: [1,2,3] }
 *   "Genesis 1:1"        → { bookAbbrev: "Gen",   chapter: 1,  verses: [1]  }
 *
 * @param reference  Human-readable reference string
 * @returns          ParsedReference or null if the format is not recognised
 *
 */
export function parseReference(reference: string): {
  bookAbbrev: string;
  chapter:    number;
  verses:     number[];
} | null {

  // Reject null/undefined/empty/whitespace-only input early.
  if (!reference?.trim()) return null;

  // Regex breakdown:
  //   ^                    — anchor at start of string start of string
  //   (\d?\s?[A-Za-z]+     — optional leading digit (for "1 Peter", "2 Samuel"), optional space,
  //                          then at least one alphabetic word ("John", "Song")
  //     (?:\s[A-Za-z]+)*)  — zero or more additional words ("of", "Solomon")
  //   \s+                  — at least one space before the chapter number
  //   (\d+)                — chapter number (one or more digits)
  //   :                    — literal colon separator
  //   (\d+)                — starting verse literal
  //   (?:-(\d+))?          — optional "-N" for verse ranges plus ending verse number
  //   $                    — anchor at end of string
  const match = reference.trim().match(
    /^(\d?\s?[A-Za-z]+(?:\s[A-Za-z]+)*)\s+(\d+):(\d+)(?:-(\d+))?$/
  );

  // If the regex fails, the reference is not in a supported format.
  if (!match) {
    console.warn(`[Bible] Cannot parse reference: "${reference}"`);
    return null;
  }

  // Extract raw components from the regex groups.
  const rawBook    = match[1].trim(); // e.g. "1 peter", "john"
  const chapter    = parseInt(match[2], 10); // chapter number
  const startVerse = parseInt(match[3], 10); // first verse in the range
  const endVerse   = match[4] ? parseInt(match[4], 10) : startVerse;
  // if no range, end = start

  // Normalise the book name to Title Case so it matches BOOK_MAP keys.
  // Examples:
  //   "john" → "John"
  //   "1 peter" → "1 Peter"
  const titleCase = rawBook
    .split(" ")
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
    .join(" ");

  // Look up the abbreviation in BOOK_MAP.
  // Prefer the normalised form, but fall back to the raw form if needed.
  const bookAbbrev = BOOK_MAP[titleCase] ?? BOOK_MAP[rawBook];

  // If no mapping exists, the book is unknown to the system.
  if (!bookAbbrev) {
    console.warn(`[Bible] Book not found in BOOK_MAP: "${rawBook}" (normalised: "${titleCase}")`);
    return null;
  }

  // Expand verse range into an explicit list.
  // Example: 1–3 → [1, 2, 3]
  const verses: number[] = [];
  for (let v = startVerse; v <= endVerse; v++) {
    verses.push(v);
  }

  // Return the fully parsed reference.
  return { bookAbbrev, chapter, verses };
}


// ══════════════════════════════════════════════════════════════════════════════
// KJV LOOKUP
// ══════════════════════════════════════════════════════════════════════════════

/**
 * Look up a single verse in the bundled KJV JSON.
 *
 * /Data shape expected from `getKJVData()`:
 * { [bookAbbrev: string]: { [chapter: string]: { [verse: string]: string } } }
 *
 * @param bookAbbrev  The `freebible` abbreviation of the biblical book
 *                     (e.g. `"Joh"`, `"Pslm"`, `"1Pe"`).
 * @param chapter     Chapter number (1‑based). Must be a positive integer.
 * @param verse       Verse number (1‑based). Must be a positive integer.
 * @returns           The plain‑text verse string if found, or `null` if the verse
 *          does not exist in the data or the data has not been loaded.
 * @remarks
 * **Error handling** – If the data hasn’t been loaded (`getKJVData()` returns
 * null), the function silently returns `null`. A `console.warn` is issued
 * only when the requested book/chapter/verse combination is absent from the
 * data (which might indicate a typo or missing entry).
 * @example
 * const verseText = lookupKJV("John", 3, 16);
 * // → "For God so loved the world, that he gave his only begotten Son..."
 *
 * const psalm = lookupKJV("Pslm", 23, 1);
 * // → "The LORD is my shepherd; I shall not want."
 */
export function lookupKJV(
  bookAbbrev: string,
  chapter:    number,
  verse:      number
): string | null {
  // Attempt to retrieve the KJV data object from the external loader.
  const kjv = getKJVData();

  // If data is unavailable (not loaded, or loading failed), bail out early.
  if (!kjv) return null;

  // Navigate the nested structure.
  // kjv[book][chapter][verse] — all keys are strings.
  const text = kjv[bookAbbrev]?.[String(chapter)]?.[String(verse)];

  // If the verse is missing, log a warning and return null.
  // This helps catch invalid references or data gaps during development.
  if (!text) {
    console.warn(`[Bible] KJV: not found — ${bookAbbrev} ${chapter}:${verse}`);
    return null;
  }

  return text;
}


/**
 * Look up a complete Bible reference (possibly containing a verse range)
 * from the KJV data and return the concatenated text.
 *
 * The reference is first parsed by `parseReference()` into its structural
 * components (book abbreviation, chapter, array of verses). Each individual
 * verse is fetched via `lookupKJV()`, and the resulting texts are joined
 * with a single space.
 *
 * @param reference  A human‑readable Bible reference string, for example
 *                    `"John 3:16"`, `"Psalm 23:1-3"`, `"1 Peter 5:7"`.
 * @returns          A `VerseResult` object containing:
 *  - `ref`    : the original reference string (unmodified)
 *  - `text`   : the joined verse texts, or an empty string if none found
 *  - `source` : the string `"KJV (offline)"` indicating the translation
 *               and data source
 *  Returns `null` only when the reference itself cannot be parsed or when
 *  **none** of the requested verses are present in the data.
 *
 * @example
 * const v = lookupKJVByRef("Psalm 23:1-3");
 * // → { ref: "Psalm 23:1-3", text: "The LORD is my shepherd...", source: "KJV (offline)" }
 */
export function lookupKJVByRef(reference: string): VerseResult | null {
  // Parse the human-readable reference into book abbreviation, chapter,
  // and an array of verse numbers.
  const parsed = parseReference(reference);
  if (!parsed) return null;

  // Collect the text of each requested verse.
  const texts: string[] = [];
  for (const verse of parsed.verses) {
    const text = lookupKJV(parsed.bookAbbrev, parsed.chapter, verse);
    if (text) texts.push(text);
  }

  // If no verses were found at all, treat it as a failure.
  if (texts.length === 0) return null;

  // (Unused) Calculate the day of year – a remnant of a previous feature
  // that might have selected a daily verse. Keep it commented.
  const dayIdx = getDayOfYear();
  // (Unused) The full BOOK_MAP is assigned to `plan` but never used.
  // This is a placeholder for future reading plan integration.
  const plan   = BOOK_MAP;

  // Assemble and return the result.
  return {
    ref:    reference,
    text:   texts.join(" "),
    source: "KJV (offline)",
  };
}

Step 4 — The KJV test panel inside the Verse tab

The goal is to add a self-contained, offline test panel that proves the King James Version (KJV) Bible lookup is working without any network connection. It’s placed on the same Verse screen as the API‑driven panel, so you can visually compare them side‑by‑side.

import {
  lookupKJVByRef,
  // the primary function we’ll use to get a verse (or verse range)
  // by a human‑readable reference like "John 3:16".
  lookupKJV,
} from "../../src/services/bibleService";

Add this component after apiStyles:

// ── KJVPanel ─────────────────────────────────────
// It’s a plain function component — no state, no effects, no hooks
// That's possible because lookupKJV is synchronous once the JSON has been loaded.
// The KJV data is bundled directly into the app as an asset (kjv.json).
function KJVPanel() {
  // The test data array: ref – the scripture reference exactly as a user might type it.
  // expected – a human‑readable label for what should appear.
  // he fourth test (Psalm 23:1-3) verifies that verse ranges are handled correctly
  const tests: Array<{ ref: string; expected: string }> = [
    { ref: "John 3:16",     expected: "For God so loved" },
    { ref: "Psalm 23:1",    expected: "shepherd" },
    { ref: "Romans 8:28",   expected: "all things work" },
    { ref: "Psalm 23:1-3",  expected: "range — 3 verses joined" },
  ];
  // The UI — a card layout
  return (
    // apiStyles is reused from the API panel. This keeps the design consistent.
    <View style={apiStyles.card}>
      <Text style={apiStyles.title}>📖 Offline KJV</Text>
      <Text style={[apiStyles.note, { marginBottom: 12 }]}>
        Works offline — no internet needed.{"\n"}
        Turn off WiFi (to prove it works offline) and refresh to confirm.
      </Text>
      // Rendering each test case.
      // For each test reference, lookupKJVByRef(ref) is called directly in the render function.
      // This is okay because it’s synchronous and fast.
      {tests.map(({ ref }) => {
        // The result object (if successful) contains: text – the verse text; source – a string "KJV (offline)".
        const result = lookupKJVByRef(ref);
        return (
          <View key={ref} style={apiStyles.resultBlock}>
            <Text style={apiStyles.resultLabel}>lookupKJVByRef("{ref}")</Text>
            {result ? (
              <> // This is a React fragment (<> … </>) -they don’t render anything in the UI tree-
                // containing the result UI.
                // This is simply a wrapper that allows you to return multiple sibling elements
                // without adding an extra View.
                // The text is truncated to 100 characters with an ellipsis for display cleanliness.
                <Text style={apiStyles.resultText}>
                  {result.text.slice(0, 100)}
                  {result.text.length > 100 ? "…" : ""}
                </Text>
                <Text style={apiStyles.resultSource}>{result.source}</Text>
              </>
            ) : (
              // If result is null or undefined, the test shows an error.
              <Text style={apiStyles.resultError}>
                ❌ null — kjv.json missing or reference not found
              </Text>
            )}
          </View>
        );
      })}
    </View>
  );
}

Add inside the ScrollView in VerseScreen, after . This adds the panel below the API‑based one, so when you scroll you can see both. The existing ScrollView already contains the API panel; now it also hosts the offline KJV panel.

Step 5 — Run and test

// --clear clears the Metro bundler cache. This ensures the latest version of kjv.json
// (if it was changed) and any newly added modules are picked up correctly.
npx expo start --clear

What to expect

  1. With kjv.json present (airplane mode safe).
  2. Navigate to the Verse tab (📖 icon). You’ll see three panels, then the new KJV panel after it:
📖 Offline KJV — Chunk 15
Works offline — no internet needed.
Turn off WiFi and refresh to confirm.

lookupKJVByRef("John 3:16")
  For God so loved the world, that he gave his only begotten Son...
  KJV (offline)

[...]
Bitcoin donation

JustToThePoint Copyright © 2011 - 2026 Anawim. ALL RIGHTS RESERVED. Bilingual e-books, articles, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun. Social Issues, Join us.

This website uses cookies to improve your navigation experience.
By continuing, you are consenting to our use of cookies, in accordance with our Cookies Policy and Website Terms and Conditions of use.