Files
TaroTime/app/ui-alphabet-gematria.js

940 lines
28 KiB
JavaScript
Raw Permalink Normal View History

2026-03-07 05:17:50 -08:00
(function () {
"use strict";
let config = {
getAlphabets: () => null,
2026-03-08 22:24:34 -07:00
getGematriaDb: () => null,
2026-03-07 05:17:50 -08:00
getGematriaElements: () => ({
cipherEl: null,
inputEl: null,
resultEl: null,
2026-03-09 03:07:02 -07:00
breakdownEl: null,
2026-03-09 14:43:03 -07:00
modeEls: [],
2026-03-09 03:07:02 -07:00
matchesEl: null,
inputLabelEl: null,
cipherLabelEl: null
2026-03-07 05:17:50 -08:00
})
};
const state = {
loadingPromise: null,
db: null,
listenersBound: false,
activeCipherId: "",
2026-03-09 03:07:02 -07:00
forwardInputText: "",
reverseInputText: "",
2026-03-09 14:43:03 -07:00
anagramInputText: "",
2026-03-20 13:39:54 -07:00
dictionaryInputText: "",
2026-03-09 03:07:02 -07:00
activeMode: "forward",
scriptCharMap: new Map(),
reverseLookupCache: new Map(),
2026-03-09 14:43:03 -07:00
anagramLookupCache: new Map(),
2026-03-20 13:39:54 -07:00
dictionaryLookupCache: new Map(),
2026-03-09 14:43:03 -07:00
reverseRequestId: 0,
2026-03-20 13:39:54 -07:00
anagramRequestId: 0,
dictionaryRequestId: 0
2026-03-07 05:17:50 -08:00
};
function getAlphabets() {
return config.getAlphabets?.() || null;
}
2026-03-08 22:24:34 -07:00
function getConfiguredGematriaDb() {
return config.getGematriaDb?.() || null;
}
2026-03-07 05:17:50 -08:00
function getElements() {
return config.getGematriaElements?.() || {
cipherEl: null,
inputEl: null,
resultEl: null,
2026-03-09 03:07:02 -07:00
breakdownEl: null,
2026-03-09 14:43:03 -07:00
modeEls: [],
2026-03-09 03:07:02 -07:00
matchesEl: null,
inputLabelEl: null,
cipherLabelEl: null
2026-03-07 05:17:50 -08:00
};
}
2026-03-09 03:07:02 -07:00
function isReverseMode() {
return state.activeMode === "reverse";
}
2026-03-09 14:43:03 -07:00
function isAnagramMode() {
return state.activeMode === "anagram";
}
2026-03-20 13:39:54 -07:00
function isDictionaryMode() {
return state.activeMode === "dictionary";
}
2026-03-09 03:07:02 -07:00
function getCurrentInputText() {
2026-03-09 14:43:03 -07:00
if (isReverseMode()) {
return state.reverseInputText;
}
if (isAnagramMode()) {
return state.anagramInputText;
}
2026-03-20 13:39:54 -07:00
if (isDictionaryMode()) {
return state.dictionaryInputText;
}
2026-03-09 14:43:03 -07:00
return state.forwardInputText;
2026-03-09 03:07:02 -07:00
}
function formatCount(value) {
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) {
return "0";
}
return numericValue.toLocaleString();
}
2026-03-07 05:17:50 -08:00
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)
2026-03-09 03:07:02 -07:00
},
{
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]
2026-03-07 05:17:50 -08:00
}
]
};
}
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;
}
2026-03-08 22:24:34 -07:00
state.loadingPromise = Promise.resolve()
.then(async () => {
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
return configuredDb;
2026-03-07 05:17:50 -08:00
}
2026-03-08 22:24:34 -07:00
const referenceData = await window.TarotDataService?.loadReferenceData?.();
return referenceData?.gematriaCiphers || null;
2026-03-07 05:17:50 -08:00
})
.then((db) => {
2026-03-08 22:24:34 -07:00
if (!db) {
throw new Error("Gematria cipher data unavailable from API.");
}
2026-03-07 05:17:50 -08:00
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;
}
2026-03-09 03:07:02 -07:00
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;
}
2026-03-09 14:43:03 -07:00
function getModeElements(modeEls) {
return Array.isArray(modeEls)
? modeEls.filter((element) => element instanceof HTMLInputElement)
: [];
}
2026-03-09 03:07:02 -07:00
function updateModeUi() {
const {
cipherEl,
inputEl,
2026-03-09 14:43:03 -07:00
modeEls,
2026-03-09 03:07:02 -07:00
matchesEl,
inputLabelEl,
cipherLabelEl
} = getElements();
const reverseMode = isReverseMode();
2026-03-09 14:43:03 -07:00
const anagramMode = isAnagramMode();
2026-03-20 13:39:54 -07:00
const dictionaryMode = isDictionaryMode();
2026-03-09 14:43:03 -07:00
const radioEls = getModeElements(modeEls);
2026-03-09 03:07:02 -07:00
2026-03-09 14:43:03 -07:00
radioEls.forEach((element) => {
element.checked = String(element.value || "") === state.activeMode;
});
2026-03-09 03:07:02 -07:00
if (inputLabelEl) {
2026-03-20 13:39:54 -07:00
inputLabelEl.textContent = reverseMode
? "Value"
2026-03-23 17:04:04 -07:00
: (anagramMode ? "Letters" : (dictionaryMode ? "Pattern" : "Text"));
2026-03-09 03:07:02 -07:00
}
if (cipherLabelEl) {
2026-03-20 13:39:54 -07:00
cipherLabelEl.textContent = (reverseMode || anagramMode || dictionaryMode) ? "Cipher (not used in this mode)" : "Cipher";
2026-03-09 03:07:02 -07:00
}
if (cipherEl) {
2026-03-20 13:39:54 -07:00
const disableCipher = reverseMode || anagramMode || dictionaryMode;
2026-03-09 14:43:03 -07:00
cipherEl.disabled = disableCipher;
2026-03-23 17:04:04 -07:00
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);
2026-03-09 03:07:02 -07:00
}
if (inputEl) {
2026-03-09 14:43:03 -07:00
inputEl.placeholder = reverseMode
? "Enter a whole number, e.g. 33"
2026-03-23 17:04:04 -07:00
: (anagramMode ? "Type letters or a word, e.g. listen" : (dictionaryMode ? "Type a pattern, e.g. lo, *ende, d?nkey" : "Type or paste text"));
2026-03-09 03:07:02 -07:00
inputEl.inputMode = reverseMode ? "numeric" : "text";
2026-03-20 13:39:54 -07:00
inputEl.spellcheck = !(reverseMode || anagramMode || dictionaryMode);
2026-03-09 03:07:02 -07:00
const nextValue = getCurrentInputText();
if (inputEl.value !== nextValue) {
inputEl.value = nextValue;
}
}
2026-03-20 13:39:54 -07:00
if (!reverseMode && !anagramMode && !dictionaryMode) {
2026-03-09 03:07:02 -07:00
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;
}
2026-03-09 14:43:03 -07:00
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;
}
2026-03-20 13:39:54 -07:00
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;
}
2026-03-23 17:04:04 -07:00
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);
}
}
2026-03-09 03:07:02 -07:00
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);
}
2026-03-23 17:04:04 -07:00
appendWordMetadata(cardEl, match);
2026-03-09 03:07:02 -07:00
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.");
}
}
2026-03-09 14:43:03 -07:00
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);
}
2026-03-23 17:04:04 -07:00
appendWordMetadata(cardEl, match);
2026-03-09 14:43:03 -07:00
fragment.appendChild(cardEl);
});
matchesEl.replaceChildren(fragment);
matchesEl.hidden = false;
}
2026-03-20 13:39:54 -07:00
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;
2026-03-23 17:04:04 -07:00
const normalizedPattern = String(payload?.normalized || state.dictionaryInputText || "").trim().toLowerCase();
const hasWildcard = Boolean(payload?.hasWildcard) || normalizedPattern.includes("*") || normalizedPattern.includes("?");
2026-03-20 13:39:54 -07:00
2026-03-23 17:04:04 -07:00
resultEl.textContent = normalizedPattern ? `Pattern: ${normalizedPattern}` : "Pattern: --";
2026-03-20 13:39:54 -07:00
if (!displayCount) {
2026-03-23 17:04:04 -07:00
breakdownEl.textContent = hasWildcard
? "No dictionary words matched this pattern."
: "No dictionary words matched this prefix.";
2026-03-20 13:39:54 -07:00
matchesEl.hidden = false;
2026-03-23 17:04:04 -07:00
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.");
2026-03-20 13:39:54 -07:00
return;
}
2026-03-23 17:04:04 -07:00
breakdownEl.textContent = hasWildcard
? `Found ${formatCount(displayCount)} words that match \"${normalizedPattern}\".`
: `Found ${formatCount(displayCount)} words that start with \"${normalizedPattern}\".`;
2026-03-20 13:39:54 -07:00
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);
}
2026-03-23 17:04:04 -07:00
appendWordMetadata(cardEl, match);
2026-03-20 13:39:54 -07:00
fragment.appendChild(cardEl);
});
matchesEl.replaceChildren(fragment);
matchesEl.hidden = false;
}
2026-03-09 14:43:03 -07:00
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.");
}
}
2026-03-20 13:39:54 -07:00
async function renderDictionaryResult() {
const { resultEl, breakdownEl, matchesEl } = getElements();
if (!resultEl || !breakdownEl || !matchesEl) {
return;
}
const rawText = state.dictionaryInputText;
if (!String(rawText || "").trim()) {
2026-03-23 17:04:04 -07:00
resultEl.textContent = "Pattern: --";
breakdownEl.textContent = "Enter opening letters or use * and ? wildcards to search dictionary words.";
2026-03-20 13:39:54 -07:00
matchesEl.hidden = false;
2026-03-23 17:04:04 -07:00
setMatchesMessage(matchesEl, "Dictionary mode supports starts-with searches and wildcards such as lo, *ende, w*rd, or d?nkey.");
2026-03-20 13:39:54 -07:00
return;
}
const requestId = state.dictionaryRequestId + 1;
state.dictionaryRequestId = requestId;
2026-03-23 17:04:04 -07:00
resultEl.textContent = "Pattern: --";
2026-03-20 13:39:54 -07:00
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;
}
2026-03-23 17:04:04 -07:00
resultEl.textContent = "Pattern: --";
2026-03-20 13:39:54 -07:00
breakdownEl.textContent = "Dictionary lookup is unavailable right now.";
matchesEl.hidden = false;
setMatchesMessage(matchesEl, "Unable to load dictionary matches from the API.");
}
}
2026-03-07 05:17:50 -08:00
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(" + ")
};
}
2026-03-09 03:07:02 -07:00
function renderForwardGematriaResult() {
2026-03-07 05:17:50 -08:00
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;
}
2026-03-09 03:07:02 -07:00
const { total, count, breakdown } = computeGematria(state.forwardInputText, cipher, db.baseAlphabet);
2026-03-07 05:17:50 -08:00
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}`;
}
2026-03-09 03:07:02 -07:00
function renderGematriaResult() {
updateModeUi();
if (isReverseMode()) {
void renderReverseLookupResult();
return;
}
2026-03-09 14:43:03 -07:00
if (isAnagramMode()) {
void renderAnagramResult();
return;
}
2026-03-20 13:39:54 -07:00
if (isDictionaryMode()) {
void renderDictionaryResult();
return;
}
2026-03-09 03:07:02 -07:00
renderForwardGematriaResult();
}
2026-03-07 05:17:50 -08:00
function bindGematriaListeners() {
2026-03-09 14:43:03 -07:00
const { cipherEl, inputEl, modeEls } = getElements();
2026-03-07 05:17:50 -08:00
if (state.listenersBound || !cipherEl || !inputEl) {
return;
}
cipherEl.addEventListener("change", () => {
state.activeCipherId = String(cipherEl.value || "").trim();
renderGematriaResult();
});
inputEl.addEventListener("input", () => {
2026-03-09 03:07:02 -07:00
if (isReverseMode()) {
state.reverseInputText = inputEl.value || "";
2026-03-09 14:43:03 -07:00
} else if (isAnagramMode()) {
state.anagramInputText = inputEl.value || "";
2026-03-20 13:39:54 -07:00
} else if (isDictionaryMode()) {
state.dictionaryInputText = inputEl.value || "";
2026-03-09 03:07:02 -07:00
} else {
state.forwardInputText = inputEl.value || "";
}
renderGematriaResult();
});
2026-03-09 14:43:03 -07:00
getModeElements(modeEls).forEach((modeEl) => {
modeEl.addEventListener("change", () => {
if (!modeEl.checked) {
return;
}
state.activeMode = String(modeEl.value || "forward").trim() || "forward";
updateModeUi();
renderGematriaResult();
});
2026-03-07 05:17:50 -08:00
});
state.listenersBound = true;
}
function ensureCalculator() {
const { cipherEl, inputEl, resultEl, breakdownEl } = getElements();
if (!cipherEl || !inputEl || !resultEl || !breakdownEl) {
return;
}
bindGematriaListeners();
2026-03-09 03:07:02 -07:00
updateModeUi();
2026-03-07 05:17:50 -08:00
void loadGematriaDb().then(() => {
refreshScriptMap((state.db || getFallbackGematriaDb()).baseAlphabet);
renderGematriaCipherOptions();
renderGematriaResult();
});
}
function init(nextConfig = {}) {
config = {
...config,
...nextConfig
};
2026-03-08 22:24:34 -07:00
const configuredDb = getConfiguredGematriaDb();
if (configuredDb) {
state.db = sanitizeGematriaDb(configuredDb);
}
2026-03-07 05:17:50 -08:00
}
window.AlphabetGematriaUi = {
...(window.AlphabetGematriaUi || {}),
init,
refreshScriptMap,
ensureCalculator
};
})();