Files
TaroTime/app/ui-alphabet-gematria.js
2026-03-23 17:04:04 -07:00

940 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
"use strict";
let config = {
getAlphabets: () => null,
getGematriaDb: () => null,
getGematriaElements: () => ({
cipherEl: null,
inputEl: null,
resultEl: null,
breakdownEl: null,
modeEls: [],
matchesEl: null,
inputLabelEl: null,
cipherLabelEl: null
})
};
const state = {
loadingPromise: null,
db: null,
listenersBound: false,
activeCipherId: "",
forwardInputText: "",
reverseInputText: "",
anagramInputText: "",
dictionaryInputText: "",
activeMode: "forward",
scriptCharMap: new Map(),
reverseLookupCache: new Map(),
anagramLookupCache: new Map(),
dictionaryLookupCache: new Map(),
reverseRequestId: 0,
anagramRequestId: 0,
dictionaryRequestId: 0
};
function getAlphabets() {
return config.getAlphabets?.() || null;
}
function getConfiguredGematriaDb() {
return config.getGematriaDb?.() || null;
}
function getElements() {
return config.getGematriaElements?.() || {
cipherEl: null,
inputEl: null,
resultEl: null,
breakdownEl: null,
modeEls: [],
matchesEl: null,
inputLabelEl: null,
cipherLabelEl: null
};
}
function isReverseMode() {
return state.activeMode === "reverse";
}
function isAnagramMode() {
return state.activeMode === "anagram";
}
function isDictionaryMode() {
return state.activeMode === "dictionary";
}
function getCurrentInputText() {
if (isReverseMode()) {
return state.reverseInputText;
}
if (isAnagramMode()) {
return state.anagramInputText;
}
if (isDictionaryMode()) {
return state.dictionaryInputText;
}
return state.forwardInputText;
}
function formatCount(value) {
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) {
return "0";
}
return numericValue.toLocaleString();
}
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)
},
{
id: "decadic-cipher",
name: "Decadic Cipher",
description: "A=1 ... I=9, J=10 ... R=90, S=100 ... Z=800",
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800]
}
]
};
}
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 = Promise.resolve()
.then(async () => {
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
return configuredDb;
}
const referenceData = await window.TarotDataService?.loadReferenceData?.();
return referenceData?.gematriaCiphers || null;
})
.then((db) => {
if (!db) {
throw new Error("Gematria cipher data unavailable from API.");
}
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 setMatchesMessage(matchesEl, message) {
if (!matchesEl) {
return;
}
matchesEl.replaceChildren();
const emptyEl = document.createElement("div");
emptyEl.className = "alpha-gematria-match-empty";
emptyEl.textContent = message;
matchesEl.appendChild(emptyEl);
}
function clearReverseMatches(matchesEl) {
if (!matchesEl) {
return;
}
matchesEl.replaceChildren();
matchesEl.hidden = true;
}
function getModeElements(modeEls) {
return Array.isArray(modeEls)
? modeEls.filter((element) => element instanceof HTMLInputElement)
: [];
}
function updateModeUi() {
const {
cipherEl,
inputEl,
modeEls,
matchesEl,
inputLabelEl,
cipherLabelEl
} = getElements();
const reverseMode = isReverseMode();
const anagramMode = isAnagramMode();
const dictionaryMode = isDictionaryMode();
const radioEls = getModeElements(modeEls);
radioEls.forEach((element) => {
element.checked = String(element.value || "") === state.activeMode;
});
if (inputLabelEl) {
inputLabelEl.textContent = reverseMode
? "Value"
: (anagramMode ? "Letters" : (dictionaryMode ? "Pattern" : "Text"));
}
if (cipherLabelEl) {
cipherLabelEl.textContent = (reverseMode || anagramMode || dictionaryMode) ? "Cipher (not used in this mode)" : "Cipher";
}
if (cipherEl) {
const disableCipher = reverseMode || anagramMode || dictionaryMode;
cipherEl.disabled = disableCipher;
const cipherFieldEl = cipherEl.closest(".alpha-gematria-field");
const controlsEl = cipherEl.closest(".alpha-gematria-controls");
cipherFieldEl?.classList.toggle("is-disabled", disableCipher);
controlsEl?.classList.toggle("is-input-priority-mode", disableCipher);
}
if (inputEl) {
inputEl.placeholder = reverseMode
? "Enter a whole number, e.g. 33"
: (anagramMode ? "Type letters or a word, e.g. listen" : (dictionaryMode ? "Type a pattern, e.g. lo, *ende, d?nkey" : "Type or paste text"));
inputEl.inputMode = reverseMode ? "numeric" : "text";
inputEl.spellcheck = !(reverseMode || anagramMode || dictionaryMode);
const nextValue = getCurrentInputText();
if (inputEl.value !== nextValue) {
inputEl.value = nextValue;
}
}
if (!reverseMode && !anagramMode && !dictionaryMode) {
clearReverseMatches(matchesEl);
}
}
function parseReverseLookupValue(rawValue) {
const normalizedValue = String(rawValue || "").trim();
if (!normalizedValue) {
return null;
}
if (!/^\d+$/.test(normalizedValue)) {
return Number.NaN;
}
const numericValue = Number(normalizedValue);
if (!Number.isSafeInteger(numericValue)) {
return Number.NaN;
}
return numericValue;
}
async function loadReverseLookup(value) {
const cacheKey = String(value);
if (state.reverseLookupCache.has(cacheKey)) {
return state.reverseLookupCache.get(cacheKey);
}
const payload = await window.TarotDataService?.loadGematriaWordsByValue?.(value);
state.reverseLookupCache.set(cacheKey, payload);
return payload;
}
async function loadAnagramLookup(text) {
const cacheKey = String(text || "").trim().toLowerCase();
if (state.anagramLookupCache.has(cacheKey)) {
return state.anagramLookupCache.get(cacheKey);
}
const payload = await window.TarotDataService?.loadWordAnagrams?.(text);
state.anagramLookupCache.set(cacheKey, payload);
return payload;
}
async function loadDictionaryLookup(prefix) {
const cacheKey = String(prefix || "").trim().toLowerCase();
if (state.dictionaryLookupCache.has(cacheKey)) {
return state.dictionaryLookupCache.get(cacheKey);
}
const payload = await window.TarotDataService?.loadWordsByPrefix?.(prefix);
state.dictionaryLookupCache.set(cacheKey, payload);
return payload;
}
function appendWordMetadata(cardEl, match) {
if (!(cardEl instanceof HTMLElement) || !match || typeof match !== "object") {
return;
}
const gematriaValue = Number(match?.gematriaValue);
const syllableValue = Number(match?.syllableValue);
if (!Number.isFinite(gematriaValue) && !Number.isFinite(syllableValue)) {
return;
}
const metaEl = document.createElement("div");
metaEl.className = "alpha-gematria-match-meta";
if (Number.isFinite(gematriaValue)) {
const gematriaEl = document.createElement("span");
gematriaEl.className = "alpha-gematria-match-meta-chip";
gematriaEl.textContent = `Simple Ordinal ${formatCount(gematriaValue)}`;
metaEl.appendChild(gematriaEl);
}
if (Number.isFinite(syllableValue)) {
const syllableEl = document.createElement("span");
syllableEl.className = "alpha-gematria-match-meta-chip";
syllableEl.textContent = `Syllables ${formatCount(syllableValue)}`;
metaEl.appendChild(syllableEl);
}
if (metaEl.childElementCount) {
cardEl.appendChild(metaEl);
}
}
function renderReverseLookupMatches(payload, numericValue) {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const matches = Array.isArray(payload?.matches) ? payload.matches : [];
const count = Number(payload?.count);
const displayCount = Number.isFinite(count) ? count : matches.length;
const ciphers = Array.isArray(payload?.ciphers) ? payload.ciphers : [];
const cipherCount = Number(payload?.cipherCount);
const displayCipherCount = Number.isFinite(cipherCount) ? cipherCount : ciphers.length;
const visibleMatches = matches.slice(0, 120);
resultEl.textContent = `Value: ${formatCount(numericValue)}`;
if (!displayCount) {
breakdownEl.textContent = "No words matched this reverse gematria value.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "No matches found in the reverse gematria index.");
return;
}
const topCipherSummary = ciphers
.slice(0, 6)
.map((cipher) => `${String(cipher?.name || cipher?.id || "Unknown")} ${formatCount(cipher?.count)}`)
.join(" · ");
breakdownEl.textContent = `Found ${formatCount(displayCount)} matches across ${formatCount(displayCipherCount)} ciphers.${topCipherSummary ? ` Top ciphers: ${topCipherSummary}.` : ""}${displayCount > visibleMatches.length ? ` Showing first ${formatCount(visibleMatches.length)}.` : ""}`;
const fragment = document.createDocumentFragment();
visibleMatches.forEach((match) => {
const cardEl = document.createElement("article");
cardEl.className = "alpha-gematria-match";
const wordEl = document.createElement("div");
wordEl.className = "alpha-gematria-match-word";
wordEl.textContent = String(match?.word || "--");
cardEl.appendChild(wordEl);
const definition = String(match?.definition || "").trim();
if (definition) {
const definitionEl = document.createElement("div");
definitionEl.className = "alpha-gematria-match-definition";
definitionEl.textContent = definition;
cardEl.appendChild(definitionEl);
}
appendWordMetadata(cardEl, match);
const ciphersEl = document.createElement("div");
ciphersEl.className = "alpha-gematria-match-ciphers";
const matchCiphers = Array.isArray(match?.ciphers) ? match.ciphers : [];
matchCiphers.slice(0, 6).forEach((cipher) => {
const chipEl = document.createElement("span");
chipEl.className = "alpha-gematria-match-cipher";
chipEl.textContent = String(cipher?.name || cipher?.id || "Unknown");
ciphersEl.appendChild(chipEl);
});
if (matchCiphers.length > 6) {
const extraEl = document.createElement("span");
extraEl.className = "alpha-gematria-match-cipher";
extraEl.textContent = `+${matchCiphers.length - 6} more`;
ciphersEl.appendChild(extraEl);
}
cardEl.appendChild(ciphersEl);
fragment.appendChild(cardEl);
});
matchesEl.replaceChildren(fragment);
matchesEl.hidden = false;
}
async function renderReverseLookupResult() {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const rawValue = state.reverseInputText;
if (!String(rawValue || "").trim()) {
resultEl.textContent = "Value: --";
breakdownEl.textContent = "Enter a whole number to find words across all available ciphers.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Reverse lookup searches the API-backed gematria word index.");
return;
}
const numericValue = parseReverseLookupValue(rawValue);
if (!Number.isFinite(numericValue)) {
resultEl.textContent = "Value: --";
breakdownEl.textContent = "Enter digits only to search the reverse gematria index.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Use a whole number such as 33 or 418.");
return;
}
const requestId = state.reverseRequestId + 1;
state.reverseRequestId = requestId;
resultEl.textContent = `Value: ${formatCount(numericValue)}`;
breakdownEl.textContent = "Searching reverse gematria index...";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Loading matching words...");
try {
const payload = await loadReverseLookup(numericValue);
if (requestId !== state.reverseRequestId || !isReverseMode()) {
return;
}
renderReverseLookupMatches(payload, numericValue);
} catch {
if (requestId !== state.reverseRequestId || !isReverseMode()) {
return;
}
resultEl.textContent = `Value: ${formatCount(numericValue)}`;
breakdownEl.textContent = "Reverse lookup is unavailable right now.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Unable to load reverse gematria words from the API.");
}
}
function renderAnagramMatches(payload) {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const matches = Array.isArray(payload?.matches) ? payload.matches : [];
const count = Number(payload?.count);
const displayCount = Number.isFinite(count) ? count : matches.length;
const visibleMatches = matches.slice(0, 120);
const letterCount = Number(payload?.letterCount);
resultEl.textContent = `Anagrams: ${formatCount(displayCount)}`;
if (!displayCount) {
breakdownEl.textContent = "No exact dictionary anagrams matched these letters.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Try another letter set or a different spelling.");
return;
}
breakdownEl.textContent = `Found ${formatCount(displayCount)} anagrams for ${formatCount(letterCount)} letters.${displayCount > visibleMatches.length ? ` Showing first ${formatCount(visibleMatches.length)}.` : ""}`;
const fragment = document.createDocumentFragment();
visibleMatches.forEach((match) => {
const cardEl = document.createElement("article");
cardEl.className = "alpha-gematria-match";
const wordEl = document.createElement("div");
wordEl.className = "alpha-gematria-match-word";
wordEl.textContent = String(match?.word || "--");
cardEl.appendChild(wordEl);
const definition = String(match?.definition || "").trim();
if (definition) {
const definitionEl = document.createElement("div");
definitionEl.className = "alpha-gematria-match-definition";
definitionEl.textContent = definition;
cardEl.appendChild(definitionEl);
}
appendWordMetadata(cardEl, match);
fragment.appendChild(cardEl);
});
matchesEl.replaceChildren(fragment);
matchesEl.hidden = false;
}
function renderDictionaryMatches(payload) {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const matches = Array.isArray(payload?.matches) ? payload.matches : [];
const count = Number(payload?.count);
const displayCount = Number.isFinite(count) ? count : matches.length;
const normalizedPattern = String(payload?.normalized || state.dictionaryInputText || "").trim().toLowerCase();
const hasWildcard = Boolean(payload?.hasWildcard) || normalizedPattern.includes("*") || normalizedPattern.includes("?");
resultEl.textContent = normalizedPattern ? `Pattern: ${normalizedPattern}` : "Pattern: --";
if (!displayCount) {
breakdownEl.textContent = hasWildcard
? "No dictionary words matched this pattern."
: "No dictionary words matched this prefix.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, hasWildcard
? "Try another pattern such as *ion, *ende, w*rd, or d?nkey."
: "Try another word start such as lo, arc, or the.");
return;
}
breakdownEl.textContent = hasWildcard
? `Found ${formatCount(displayCount)} words that match \"${normalizedPattern}\".`
: `Found ${formatCount(displayCount)} words that start with \"${normalizedPattern}\".`;
const fragment = document.createDocumentFragment();
matches.forEach((match) => {
const cardEl = document.createElement("article");
cardEl.className = "alpha-gematria-match";
const wordEl = document.createElement("div");
wordEl.className = "alpha-gematria-match-word";
wordEl.textContent = String(match?.word || "--");
cardEl.appendChild(wordEl);
const definition = String(match?.definition || "").trim();
if (definition) {
const definitionEl = document.createElement("div");
definitionEl.className = "alpha-gematria-match-definition";
definitionEl.textContent = definition;
cardEl.appendChild(definitionEl);
}
appendWordMetadata(cardEl, match);
fragment.appendChild(cardEl);
});
matchesEl.replaceChildren(fragment);
matchesEl.hidden = false;
}
async function renderAnagramResult() {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const rawText = state.anagramInputText;
if (!String(rawText || "").trim()) {
resultEl.textContent = "Anagrams: --";
breakdownEl.textContent = "Enter letters to search for exact dictionary anagrams.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Anagram Maker finds exact word anagrams from the API word index.");
return;
}
const requestId = state.anagramRequestId + 1;
state.anagramRequestId = requestId;
resultEl.textContent = "Anagrams: --";
breakdownEl.textContent = "Searching anagram index...";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Loading anagrams...");
try {
const payload = await loadAnagramLookup(rawText);
if (requestId !== state.anagramRequestId || !isAnagramMode()) {
return;
}
renderAnagramMatches(payload);
} catch {
if (requestId !== state.anagramRequestId || !isAnagramMode()) {
return;
}
resultEl.textContent = "Anagrams: --";
breakdownEl.textContent = "Anagram lookup is unavailable right now.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Unable to load anagrams from the API.");
}
}
async function renderDictionaryResult() {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const rawText = state.dictionaryInputText;
if (!String(rawText || "").trim()) {
resultEl.textContent = "Pattern: --";
breakdownEl.textContent = "Enter opening letters or use * and ? wildcards to search dictionary words.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Dictionary mode supports starts-with searches and wildcards such as lo, *ende, w*rd, or d?nkey.");
return;
}
const requestId = state.dictionaryRequestId + 1;
state.dictionaryRequestId = requestId;
resultEl.textContent = "Pattern: --";
breakdownEl.textContent = "Searching dictionary index...";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Loading matching words...");
try {
const payload = await loadDictionaryLookup(rawText);
if (requestId !== state.dictionaryRequestId || !isDictionaryMode()) {
return;
}
renderDictionaryMatches(payload);
} catch {
if (requestId !== state.dictionaryRequestId || !isDictionaryMode()) {
return;
}
resultEl.textContent = "Pattern: --";
breakdownEl.textContent = "Dictionary lookup is unavailable right now.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Unable to load dictionary matches from the API.");
}
}
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 renderForwardGematriaResult() {
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.forwardInputText, 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 renderGematriaResult() {
updateModeUi();
if (isReverseMode()) {
void renderReverseLookupResult();
return;
}
if (isAnagramMode()) {
void renderAnagramResult();
return;
}
if (isDictionaryMode()) {
void renderDictionaryResult();
return;
}
renderForwardGematriaResult();
}
function bindGematriaListeners() {
const { cipherEl, inputEl, modeEls } = getElements();
if (state.listenersBound || !cipherEl || !inputEl) {
return;
}
cipherEl.addEventListener("change", () => {
state.activeCipherId = String(cipherEl.value || "").trim();
renderGematriaResult();
});
inputEl.addEventListener("input", () => {
if (isReverseMode()) {
state.reverseInputText = inputEl.value || "";
} else if (isAnagramMode()) {
state.anagramInputText = inputEl.value || "";
} else if (isDictionaryMode()) {
state.dictionaryInputText = inputEl.value || "";
} else {
state.forwardInputText = inputEl.value || "";
}
renderGematriaResult();
});
getModeElements(modeEls).forEach((modeEl) => {
modeEl.addEventListener("change", () => {
if (!modeEl.checked) {
return;
}
state.activeMode = String(modeEl.value || "forward").trim() || "forward";
updateModeUi();
renderGematriaResult();
});
});
state.listenersBound = true;
}
function ensureCalculator() {
const { cipherEl, inputEl, resultEl, breakdownEl } = getElements();
if (!cipherEl || !inputEl || !resultEl || !breakdownEl) {
return;
}
bindGematriaListeners();
updateModeUi();
void loadGematriaDb().then(() => {
refreshScriptMap((state.db || getFallbackGematriaDb()).baseAlphabet);
renderGematriaCipherOptions();
renderGematriaResult();
});
}
function init(nextConfig = {}) {
config = {
...config,
...nextConfig
};
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
state.db = sanitizeGematriaDb(configuredDb);
}
}
window.AlphabetGematriaUi = {
...(window.AlphabetGematriaUi || {}),
init,
refreshScriptMap,
ensureCalculator
};
})();