(function () { "use strict"; let config = { getAlphabets: () => null, getGematriaElements: () => ({ cipherEl: null, inputEl: null, resultEl: null, breakdownEl: null }) }; const state = { loadingPromise: null, db: null, listenersBound: false, activeCipherId: "", inputText: "", scriptCharMap: new Map() }; function getAlphabets() { return config.getAlphabets?.() || null; } function getElements() { return config.getGematriaElements?.() || { cipherEl: null, inputEl: null, resultEl: null, breakdownEl: null }; } function getFallbackGematriaDb() { return { baseAlphabet: "abcdefghijklmnopqrstuvwxyz", ciphers: [ { id: "simple-ordinal", name: "Simple Ordinal", description: "A=1 ... Z=26", values: Array.from({ length: 26 }, (_, index) => index + 1) } ] }; } function normalizeGematriaText(value) { return String(value || "") .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .toLowerCase(); } function transliterationToBaseLetters(transliteration, baseAlphabet) { const normalized = normalizeGematriaText(transliteration); if (!normalized) { return ""; } const primaryVariant = normalized.split(/[\/,;|]/)[0] || normalized; const primaryLetters = [...primaryVariant].filter((char) => baseAlphabet.includes(char)); if (primaryLetters.length) { return primaryLetters[0]; } const allLetters = [...normalized].filter((char) => baseAlphabet.includes(char)); return allLetters[0] || ""; } function addScriptCharMapEntry(map, scriptChar, mappedLetters) { const key = String(scriptChar || "").trim(); const value = String(mappedLetters || "").trim(); if (!key || !value) { return; } map.set(key, value); } function buildGematriaScriptMap(baseAlphabet) { const map = new Map(); const alphabets = getAlphabets() || {}; const hebrewLetters = Array.isArray(alphabets.hebrew) ? alphabets.hebrew : []; const greekLetters = Array.isArray(alphabets.greek) ? alphabets.greek : []; hebrewLetters.forEach((entry) => { const mapped = transliterationToBaseLetters(entry?.transliteration, baseAlphabet); addScriptCharMapEntry(map, entry?.char, mapped); }); greekLetters.forEach((entry) => { const mapped = transliterationToBaseLetters(entry?.transliteration, baseAlphabet); addScriptCharMapEntry(map, entry?.char, mapped); addScriptCharMapEntry(map, entry?.charLower, mapped); addScriptCharMapEntry(map, entry?.charFinal, mapped); }); const hebrewFinalForms = { ך: "k", ם: "m", ן: "n", ף: "p", ץ: "t" }; Object.entries(hebrewFinalForms).forEach(([char, mapped]) => { if (!map.has(char) && baseAlphabet.includes(mapped)) { addScriptCharMapEntry(map, char, mapped); } }); if (!map.has("ς") && baseAlphabet.includes("s")) { addScriptCharMapEntry(map, "ς", "s"); } return map; } function refreshScriptMap(baseAlphabetOverride = "") { const db = state.db || getFallbackGematriaDb(); const baseAlphabet = String(baseAlphabetOverride || db.baseAlphabet || "abcdefghijklmnopqrstuvwxyz").toLowerCase(); state.scriptCharMap = buildGematriaScriptMap(baseAlphabet); } function sanitizeGematriaDb(db) { const baseAlphabet = String(db?.baseAlphabet || "abcdefghijklmnopqrstuvwxyz").toLowerCase(); const ciphers = Array.isArray(db?.ciphers) ? db.ciphers .map((cipher) => { const id = String(cipher?.id || "").trim(); const name = String(cipher?.name || "").trim(); const values = Array.isArray(cipher?.values) ? cipher.values.map((value) => Number(value)) : []; if (!id || !name || values.length !== baseAlphabet.length || values.some((value) => !Number.isFinite(value))) { return null; } return { id, name, description: String(cipher?.description || "").trim(), values }; }) .filter(Boolean) : []; if (!ciphers.length) { return getFallbackGematriaDb(); } return { baseAlphabet, ciphers }; } async function loadGematriaDb() { if (state.db) { return state.db; } if (state.loadingPromise) { return state.loadingPromise; } state.loadingPromise = fetch("data/gematria-ciphers.json") .then((response) => { if (!response.ok) { throw new Error(`Failed to load gematria ciphers (${response.status})`); } return response.json(); }) .then((db) => { state.db = sanitizeGematriaDb(db); return state.db; }) .catch(() => { state.db = getFallbackGematriaDb(); return state.db; }) .finally(() => { state.loadingPromise = null; }); return state.loadingPromise; } function getActiveGematriaCipher() { const db = state.db || getFallbackGematriaDb(); const ciphers = Array.isArray(db.ciphers) ? db.ciphers : []; if (!ciphers.length) { return null; } const selectedId = state.activeCipherId || ciphers[0].id; return ciphers.find((cipher) => cipher.id === selectedId) || ciphers[0]; } function renderGematriaCipherOptions() { const { cipherEl } = getElements(); if (!cipherEl) { return; } const db = state.db || getFallbackGematriaDb(); const ciphers = Array.isArray(db.ciphers) ? db.ciphers : []; cipherEl.innerHTML = ""; ciphers.forEach((cipher) => { const option = document.createElement("option"); option.value = cipher.id; option.textContent = cipher.name; if (cipher.description) { option.title = cipher.description; } cipherEl.appendChild(option); }); const activeCipher = getActiveGematriaCipher(); state.activeCipherId = activeCipher?.id || ""; cipherEl.value = state.activeCipherId; } function computeGematria(text, cipher, baseAlphabet) { const normalizedInput = normalizeGematriaText(text); const scriptMap = state.scriptCharMap instanceof Map ? state.scriptCharMap : new Map(); const letterParts = []; let total = 0; let count = 0; [...normalizedInput].forEach((char) => { const mappedLetters = baseAlphabet.includes(char) ? char : (scriptMap.get(char) || ""); if (!mappedLetters) { return; } [...mappedLetters].forEach((mappedChar) => { const index = baseAlphabet.indexOf(mappedChar); if (index < 0) { return; } const value = Number(cipher.values[index]); if (!Number.isFinite(value)) { return; } count += 1; total += value; letterParts.push(`${mappedChar.toUpperCase()}(${value})`); }); }); return { total, count, breakdown: letterParts.join(" + ") }; } function renderGematriaResult() { const { resultEl, breakdownEl } = getElements(); if (!resultEl || !breakdownEl) { return; } const db = state.db || getFallbackGematriaDb(); if (!(state.scriptCharMap instanceof Map) || !state.scriptCharMap.size) { refreshScriptMap(db.baseAlphabet); } const cipher = getActiveGematriaCipher(); if (!cipher) { resultEl.textContent = "Total: --"; breakdownEl.textContent = "No ciphers available."; return; } const { total, count, breakdown } = computeGematria(state.inputText, cipher, db.baseAlphabet); resultEl.textContent = `Total: ${total}`; if (!count) { breakdownEl.textContent = `Using ${cipher.name}. Enter English, Greek, or Hebrew letters to calculate.`; return; } breakdownEl.textContent = `${cipher.name} · ${count} letters · ${breakdown} = ${total}`; } function bindGematriaListeners() { const { cipherEl, inputEl } = getElements(); if (state.listenersBound || !cipherEl || !inputEl) { return; } cipherEl.addEventListener("change", () => { state.activeCipherId = String(cipherEl.value || "").trim(); renderGematriaResult(); }); inputEl.addEventListener("input", () => { state.inputText = inputEl.value || ""; renderGematriaResult(); }); state.listenersBound = true; } function ensureCalculator() { const { cipherEl, inputEl, resultEl, breakdownEl } = getElements(); if (!cipherEl || !inputEl || !resultEl || !breakdownEl) { return; } bindGematriaListeners(); if (inputEl.value !== state.inputText) { inputEl.value = state.inputText; } void loadGematriaDb().then(() => { refreshScriptMap((state.db || getFallbackGematriaDb()).baseAlphabet); renderGematriaCipherOptions(); renderGematriaResult(); }); } function init(nextConfig = {}) { config = { ...config, ...nextConfig }; } window.AlphabetGematriaUi = { ...(window.AlphabetGematriaUi || {}), init, refreshScriptMap, ensureCalculator }; })();