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 ,
2026-04-13 14:28:03 -07:00
cipherLabelEl : null ,
reverseCiphersEl : null ,
2026-07-06 19:38:49 -07:00
reverseCipherHintEl : null ,
keyboardEl : null ,
keyboardScriptEl : null ,
keyboardGridEl : null ,
keyboardActionsEl : 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-04-13 14:28:03 -07:00
reverseSelectedCipherIds : null ,
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 ,
2026-07-06 19:38:49 -07:00
dictionaryRequestId : 0 ,
keyboardScriptId : "english"
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 ,
2026-04-13 14:28:03 -07:00
cipherLabelEl : null ,
reverseCiphersEl : null ,
2026-07-06 19:38:49 -07:00
reverseCipherHintEl : null ,
keyboardEl : null ,
keyboardScriptEl : null ,
keyboardGridEl : null ,
keyboardActionsEl : null
2026-03-07 05:17:50 -08:00
};
}
2026-07-06 19:38:49 -07:00
function uniqueChars ( values ) {
return Array . from ( new Set (( Array . isArray ( values ) ? values : [])
. map (( value ) => String ( value || "" ). trim ())
. filter ( Boolean )));
}
function collectAlphabetChars ( entries , keys ) {
const sourceEntries = Array . isArray ( entries ) ? entries : [];
const sourceKeys = Array . isArray ( keys ) ? keys : [];
const chars = [];
sourceEntries . forEach (( entry ) => {
sourceKeys . forEach (( key ) => {
const value = String ( entry ? .[ key ] || "" ). trim ();
if ( ! value ) {
return ;
}
const firstChar = [... value ][ 0 ] || "" ;
if ( firstChar ) {
chars . push ( firstChar );
}
});
});
return uniqueChars ( chars );
}
function getKeyboardLayouts () {
const db = state . db || getFallbackGematriaDb ();
const alphabets = getAlphabets () || {};
const englishChars = uniqueChars ( String ( db . baseAlphabet || "abcdefghijklmnopqrstuvwxyz" ). toLowerCase (). split ( "" ));
const hebrewChars = collectAlphabetChars ( alphabets . hebrew , [ "char" ]);
const greekChars = collectAlphabetChars ([...( alphabets . greek || []), ...( alphabets . greekArchaic || [])], [ "charLower" , "char" , "charFinal" ]);
const arabicChars = collectAlphabetChars ( alphabets . arabic , [ "char" ]);
const enochianChars = collectAlphabetChars ( alphabets . enochian , [ "char" ]);
return [
{ id : "english" , label : "English" , chars : englishChars },
{ id : "hebrew" , label : "Hebrew" , chars : hebrewChars },
{ id : "greek" , label : "Greek" , chars : greekChars },
{ id : "arabic" , label : "Arabic" , chars : arabicChars },
{ id : "enochian" , label : "Enochian" , chars : enochianChars }
]. filter (( layout ) => layout . chars . length > 0 );
}
2026-07-11 13:35:38 -07:00
function setGematriaInputValue ( nextValue , options = {}) {
2026-07-06 19:38:49 -07:00
const { inputEl } = getElements ();
if ( ! ( inputEl instanceof HTMLTextAreaElement )) {
return ;
}
2026-07-11 13:35:38 -07:00
const shouldFocus = options ? . focus !== false ;
2026-07-06 19:38:49 -07:00
inputEl . value = String ( nextValue || "" );
inputEl . dispatchEvent ( new Event ( "input" , { bubbles : true }));
2026-07-11 13:35:38 -07:00
if ( shouldFocus ) {
inputEl . focus ({ preventScroll : true });
}
2026-07-06 19:38:49 -07:00
}
2026-07-11 13:35:38 -07:00
function insertGematriaText ( insertValue , options = {}) {
2026-07-06 19:38:49 -07:00
const { inputEl } = getElements ();
if ( ! ( inputEl instanceof HTMLTextAreaElement )) {
return ;
}
const text = String ( insertValue || "" );
if ( ! text ) {
return ;
}
2026-07-11 13:35:38 -07:00
const isInputFocused = document . activeElement === inputEl ;
const useSelection = options ? . preserveSelection !== false && isInputFocused ;
const start = useSelection && Number . isFinite ( inputEl . selectionStart ) ? inputEl . selectionStart : inputEl . value . length ;
const end = useSelection && Number . isFinite ( inputEl . selectionEnd ) ? inputEl . selectionEnd : start ;
2026-07-06 19:38:49 -07:00
const nextValue = ` ${ inputEl . value . slice ( 0 , start ) }${ text }${ inputEl . value . slice ( end ) } ` ;
2026-07-11 13:35:38 -07:00
setGematriaInputValue ( nextValue , { focus : options ? . focus });
2026-07-06 19:38:49 -07:00
const nextCaret = start + text . length ;
2026-07-11 13:35:38 -07:00
if ( useSelection ) {
inputEl . setSelectionRange ( nextCaret , nextCaret );
}
2026-07-06 19:38:49 -07:00
}
2026-07-11 13:35:38 -07:00
function backspaceGematriaText ( options = {}) {
2026-07-06 19:38:49 -07:00
const { inputEl } = getElements ();
if ( ! ( inputEl instanceof HTMLTextAreaElement )) {
return ;
}
2026-07-11 13:35:38 -07:00
const isInputFocused = document . activeElement === inputEl ;
const useSelection = options ? . preserveSelection !== false && isInputFocused ;
const start = useSelection && Number . isFinite ( inputEl . selectionStart ) ? inputEl . selectionStart : inputEl . value . length ;
const end = useSelection && Number . isFinite ( inputEl . selectionEnd ) ? inputEl . selectionEnd : start ;
2026-07-06 19:38:49 -07:00
if ( start === 0 && end === 0 ) {
return ;
}
let nextStart = start ;
let nextEnd = end ;
if ( start === end ) {
nextStart = Math . max ( 0 , start - 1 );
}
const nextValue = ` ${ inputEl . value . slice ( 0 , nextStart ) }${ inputEl . value . slice ( nextEnd ) } ` ;
2026-07-11 13:35:38 -07:00
setGematriaInputValue ( nextValue , { focus : options ? . focus });
if ( useSelection ) {
inputEl . setSelectionRange ( nextStart , nextStart );
}
2026-07-06 19:38:49 -07:00
}
function renderKeyboardLayout () {
const { keyboardScriptEl , keyboardGridEl } = getElements ();
if ( ! ( keyboardScriptEl instanceof HTMLSelectElement ) || ! ( keyboardGridEl instanceof HTMLElement )) {
return ;
}
const layouts = getKeyboardLayouts ();
if ( ! layouts . length ) {
keyboardScriptEl . replaceChildren ();
keyboardGridEl . replaceChildren ();
return ;
}
const preferredLayoutId = String ( state . keyboardScriptId || "english" ). trim ();
const activeLayout = layouts . find (( layout ) => layout . id === preferredLayoutId ) || layouts [ 0 ];
state . keyboardScriptId = activeLayout . id ;
keyboardScriptEl . replaceChildren ();
layouts . forEach (( layout ) => {
const optionEl = document . createElement ( "option" );
optionEl . value = layout . id ;
optionEl . textContent = layout . label ;
keyboardScriptEl . appendChild ( optionEl );
});
keyboardScriptEl . value = state . keyboardScriptId ;
const fragment = document . createDocumentFragment ();
activeLayout . chars . forEach (( char ) => {
const buttonEl = document . createElement ( "button" );
buttonEl . type = "button" ;
buttonEl . className = "alpha-gematria-keyboard-key" ;
buttonEl . dataset . keyboardInsert = char ;
buttonEl . textContent = char ;
fragment . appendChild ( buttonEl );
});
keyboardGridEl . replaceChildren ( fragment );
keyboardGridEl . setAttribute ( "lang" , state . keyboardScriptId );
}
2026-07-11 13:35:38 -07:00
function handleKeyboardAction ( action , options = {}) {
2026-07-06 19:38:49 -07:00
const normalizedAction = String ( action || "" ). trim (). toLowerCase ();
if ( ! normalizedAction ) {
return ;
}
if ( normalizedAction === "backspace" ) {
2026-07-11 13:35:38 -07:00
backspaceGematriaText ( options );
2026-07-06 19:38:49 -07:00
return ;
}
if ( normalizedAction === "space" ) {
2026-07-11 13:35:38 -07:00
insertGematriaText ( " " , options );
2026-07-06 19:38:49 -07:00
return ;
}
if ( normalizedAction === "star" ) {
2026-07-11 13:35:38 -07:00
insertGematriaText ( "*" , options );
2026-07-06 19:38:49 -07:00
return ;
}
if ( normalizedAction === "question" ) {
2026-07-11 13:35:38 -07:00
insertGematriaText ( "?" , options );
2026-07-06 19:38:49 -07:00
return ;
}
if ( normalizedAction === "clear" ) {
2026-07-11 13:35:38 -07:00
setGematriaInputValue ( "" , { focus : options ? . focus });
2026-07-06 19:38:49 -07: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 ();
2026-07-06 19:38:49 -07:00
if ( ! key ) {
return ;
}
const value = typeof mappedLetters === "string"
? { mappedLetters : String ( mappedLetters || "" ). trim () }
: mappedLetters ;
if ( ! value || ( typeof value === "object" && ! Number . isFinite ( Number ( value . ordinal )) && ! String ( value . mappedLetters || "" ). trim ())) {
2026-03-07 05:17:50 -08:00
return ;
}
map . set ( key , value );
}
2026-07-06 19:38:49 -07:00
function getEntryOrdinal ( entry , fallbackOrdinal ) {
const explicitOrdinal = Number ( entry ? . index );
if ( Number . isFinite ( explicitOrdinal ) && explicitOrdinal > 0 ) {
return Math . trunc ( explicitOrdinal );
}
return Math . trunc ( Number ( fallbackOrdinal ) || 0 );
}
function addScriptOrdinalEntries ( map , entries , charKeys ) {
const sourceEntries = Array . isArray ( entries ) ? entries : [];
const sourceCharKeys = Array . isArray ( charKeys ) ? charKeys : [];
sourceEntries . forEach (( entry , entryIndex ) => {
const ordinal = getEntryOrdinal ( entry , entryIndex + 1 );
if ( ! ordinal ) {
return ;
}
sourceCharKeys . forEach (( charKey ) => {
const charValue = String ( entry ? .[ charKey ] || "" ). trim ();
if ( ! charValue ) {
return ;
}
const firstChar = [... charValue ][ 0 ] || "" ;
if ( ! firstChar ) {
return ;
}
addScriptCharMapEntry ( map , firstChar , {
ordinal ,
displayChar : firstChar
});
});
});
}
2026-03-07 05:17:50 -08:00
function buildGematriaScriptMap ( baseAlphabet ) {
const map = new Map ();
const alphabets = getAlphabets () || {};
const hebrewLetters = Array . isArray ( alphabets . hebrew ) ? alphabets . hebrew : [];
2026-06-01 13:15:12 -07:00
const greekClassicalLetters = Array . isArray ( alphabets . greek ) ? alphabets . greek : [];
const greekArchaicLetters = Array . isArray ( alphabets . greekArchaic ) ? alphabets . greekArchaic : [];
const greekLetters = [... greekClassicalLetters , ... greekArchaicLetters ];
2026-07-06 19:38:49 -07:00
const arabicLetters = Array . isArray ( alphabets . arabic ) ? alphabets . arabic : [];
const enochianLetters = Array . isArray ( alphabets . enochian ) ? alphabets . enochian : [];
addScriptOrdinalEntries ( map , hebrewLetters , [ "char" ]);
addScriptOrdinalEntries ( map , greekLetters , [ "char" , "charLower" , "charFinal" ]);
addScriptOrdinalEntries ( map , arabicLetters , [ "char" ]);
addScriptOrdinalEntries ( map , enochianLetters , [ "char" ]);
2026-03-07 05:17:50 -08:00
hebrewLetters . forEach (( entry ) => {
const mapped = transliterationToBaseLetters ( entry ? . transliteration , baseAlphabet );
2026-07-06 19:38:49 -07:00
if ( ! map . has ( String ( entry ? . char || "" ). trim ())) {
addScriptCharMapEntry ( map , entry ? . char , mapped );
}
2026-03-07 05:17:50 -08:00
});
greekLetters . forEach (( entry ) => {
const mapped = transliterationToBaseLetters ( entry ? . transliteration , baseAlphabet );
2026-07-06 19:38:49 -07:00
if ( ! map . has ( String ( entry ? . char || "" ). trim ())) {
addScriptCharMapEntry ( map , entry ? . char , mapped );
}
if ( ! map . has ( String ( entry ? . charLower || "" ). trim ())) {
addScriptCharMapEntry ( map , entry ? . charLower , mapped );
}
if ( ! map . has ( String ( entry ? . charFinal || "" ). trim ())) {
addScriptCharMapEntry ( map , entry ? . charFinal , mapped );
2026-03-07 05:17:50 -08:00
}
});
2026-07-06 19:38:49 -07:00
const hebrewFinalForms = {
ך : "כ" ,
ם : "מ" ,
ן : "נ" ,
ף : "פ" ,
ץ : "צ"
};
Object . entries ( hebrewFinalForms ). forEach (([ char , sourceChar ]) => {
const sourceEntry = map . get ( sourceChar );
if ( ! map . has ( char ) && sourceEntry ) {
addScriptCharMapEntry ( map , char , sourceEntry );
}
});
if ( ! map . has ( "ς" ) && map . has ( "σ " )) {
addScriptCharMapEntry ( map , "ς" , map . get ( "σ " ));
2026-03-07 05:17:50 -08:00
}
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 ];
}
2026-04-13 14:28:03 -07:00
function getGematriaCiphers () {
const db = state . db || getFallbackGematriaDb ();
return Array . isArray ( db . ciphers ) ? db . ciphers : [];
}
function normalizeSelectedReverseCipherIds ( rawCipherIds ) {
const ciphers = getGematriaCiphers ();
const requestedIds = Array . isArray ( rawCipherIds )
? rawCipherIds . map (( cipherId ) => String ( cipherId || "" ). trim ()). filter ( Boolean )
: [];
const requestedIdSet = new Set ( requestedIds );
return ciphers
. map (( cipher ) => cipher . id )
. filter (( cipherId ) => requestedIdSet . has ( cipherId ));
}
function getDefaultReverseCipherIds () {
const activeCipher = getActiveGematriaCipher ();
if ( activeCipher ? . id ) {
return [ activeCipher . id ];
}
const firstCipher = getGematriaCiphers ()[ 0 ];
return firstCipher ? . id ? [ firstCipher . id ] : [];
}
function getSelectedReverseCipherIds () {
if ( state . reverseSelectedCipherIds === null ) {
return getDefaultReverseCipherIds ();
}
return normalizeSelectedReverseCipherIds ( state . reverseSelectedCipherIds );
}
function setSelectedReverseCipherIds ( rawCipherIds ) {
state . reverseSelectedCipherIds = normalizeSelectedReverseCipherIds ( rawCipherIds );
}
function renderReverseCipherOptions () {
const { reverseCiphersEl } = getElements ();
if ( ! reverseCiphersEl ) {
return ;
}
const ciphers = getGematriaCiphers ();
const selectedCipherIds = new Set ( getSelectedReverseCipherIds ());
const fragment = document . createDocumentFragment ();
ciphers . forEach (( cipher ) => {
const optionEl = document . createElement ( "label" );
optionEl . className = "alpha-gematria-reverse-cipher-option" ;
const checkboxEl = document . createElement ( "input" );
checkboxEl . type = "checkbox" ;
checkboxEl . value = cipher . id ;
checkboxEl . checked = selectedCipherIds . has ( cipher . id );
checkboxEl . setAttribute ( "aria-label" , cipher . name );
optionEl . appendChild ( checkboxEl );
const textEl = document . createElement ( "span" );
textEl . className = "alpha-gematria-reverse-cipher-name" ;
textEl . textContent = cipher . name ;
if ( cipher . description ) {
textEl . title = cipher . description ;
}
optionEl . appendChild ( textEl );
fragment . appendChild ( optionEl );
});
reverseCiphersEl . replaceChildren ( fragment );
}
2026-03-07 05:17:50 -08:00
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-04-13 14:28:03 -07:00
if ( state . reverseSelectedCipherIds === null ) {
state . reverseSelectedCipherIds = getDefaultReverseCipherIds ();
} else {
state . reverseSelectedCipherIds = normalizeSelectedReverseCipherIds ( state . reverseSelectedCipherIds );
}
renderReverseCipherOptions ();
2026-03-07 05:17:50 -08:00
}
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 ,
2026-04-13 14:28:03 -07:00
cipherLabelEl ,
reverseCiphersEl ,
2026-07-06 19:38:49 -07:00
reverseCipherHintEl ,
keyboardEl ,
keyboardScriptEl ,
keyboardGridEl ,
keyboardActionsEl
2026-03-09 03:07:02 -07:00
} = getElements ();
const reverseMode = isReverseMode ();
2026-03-09 14:43:03 -07:00
const anagramMode = isAnagramMode ();
2026-04-13 14:28:03 -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-04-13 14:28:03 -07:00
cipherLabelEl . textContent = reverseMode
? "Ciphers"
: (( 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-04-13 14:28:03 -07:00
const hideCipherField = anagramMode || dictionaryMode ;
2026-03-09 14:43:03 -07:00
cipherEl . disabled = disableCipher ;
2026-04-13 14:28:03 -07:00
cipherEl . hidden = reverseMode ;
2026-03-23 17:04:04 -07:00
const cipherFieldEl = cipherEl . closest ( ".alpha-gematria-field" );
const controlsEl = cipherEl . closest ( ".alpha-gematria-controls" );
2026-04-13 14:28:03 -07:00
cipherFieldEl ? . classList . toggle ( "is-disabled" , hideCipherField );
controlsEl ? . classList . toggle ( "is-input-priority-mode" , hideCipherField );
controlsEl ? . classList . toggle ( "is-reverse-cipher-mode" , reverseMode );
}
if ( reverseCiphersEl ) {
if ( reverseMode ) {
renderReverseCipherOptions ();
}
reverseCiphersEl . hidden = ! reverseMode ;
}
if ( reverseCipherHintEl ) {
reverseCipherHintEl . hidden = ! reverseMode ;
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-07-06 19:38:49 -07:00
if ( keyboardEl ) {
keyboardEl . hidden = reverseMode ;
}
if ( keyboardScriptEl ) {
keyboardScriptEl . disabled = reverseMode ;
}
if ( keyboardGridEl ) {
keyboardGridEl . classList . toggle ( "is-disabled" , reverseMode );
}
if ( keyboardActionsEl ) {
keyboardActionsEl . classList . toggle ( "is-disabled" , reverseMode );
}
2026-03-20 13:39:54 -07:00
if ( ! reverseMode && ! anagramMode && ! dictionaryMode ) {
2026-03-09 03:07:02 -07:00
clearReverseMatches ( matchesEl );
}
2026-07-06 19:38:49 -07:00
if ( ! reverseMode ) {
renderKeyboardLayout ();
}
2026-03-09 03:07:02 -07:00
}
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 ) {
2026-04-13 14:28:03 -07:00
const selectedCipherIds = getSelectedReverseCipherIds ();
const cacheKey = ` ${ String ( value ) } :: ${ selectedCipherIds . join ( "," ) } ` ;
2026-03-09 03:07:02 -07:00
if ( state . reverseLookupCache . has ( cacheKey )) {
return state . reverseLookupCache . get ( cacheKey );
}
2026-04-13 14:28:03 -07:00
const payload = await window . TarotDataService ? . loadGematriaWordsByValue ? .( value , {
ciphers : selectedCipherIds
});
2026-03-09 03:07:02 -07:00
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 ( " · " );
2026-04-13 14:28:03 -07:00
breakdownEl . textContent = `Found ${ formatCount ( displayCount ) } matches across ${ formatCount ( displayCipherCount ) } selected ciphers. ${ topCipherSummary ? ` Top ciphers: ${ topCipherSummary } .` : "" }${ displayCount > visibleMatches . length ? ` Showing first ${ formatCount ( visibleMatches . length ) } .` : "" } ` ;
2026-03-09 03:07:02 -07:00
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 ;
2026-04-13 14:28:03 -07:00
const selectedCipherIds = getSelectedReverseCipherIds ();
2026-03-09 03:07:02 -07:00
if ( ! String ( rawValue || "" ). trim ()) {
resultEl . textContent = "Value: --" ;
2026-04-13 14:28:03 -07:00
breakdownEl . textContent = "Enter a whole number and choose one or more ciphers to narrow reverse matches." ;
2026-03-09 03:07:02 -07:00
matchesEl . hidden = false ;
2026-04-13 14:28:03 -07:00
setMatchesMessage ( matchesEl , "Reverse lookup searches the API-backed gematria word index using the selected ciphers only." );
return ;
}
if ( ! selectedCipherIds . length ) {
resultEl . textContent = "Value: --" ;
breakdownEl . textContent = "Choose at least one cipher before running reverse lookup." ;
matchesEl . hidden = false ;
setMatchesMessage ( matchesEl , "Select one or more ciphers to limit reverse gematria matches." );
2026-03-09 03:07:02 -07:00
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 ) => {
2026-07-06 19:38:49 -07:00
if ( baseAlphabet . includes ( char )) {
const index = baseAlphabet . indexOf ( char );
if ( index < 0 ) {
return ;
}
2026-03-07 05:17:50 -08:00
2026-07-06 19:38:49 -07:00
const value = Number ( cipher . values [ index ]);
if ( ! Number . isFinite ( value )) {
return ;
}
count += 1 ;
total += value ;
letterParts . push ( ` ${ char . toUpperCase () } ( ${ value } )` );
return ;
}
const mappedEntry = scriptMap . get ( char );
if ( ! mappedEntry ) {
return ;
}
const ordinal = Number ( mappedEntry . ordinal );
if ( Number . isFinite ( ordinal ) && ordinal > 0 ) {
const index = Math . trunc ( ordinal ) - 1 ;
const value = Number ( cipher . values [ index ]);
if ( ! Number . isFinite ( value )) {
return ;
}
const displayChar = String ( mappedEntry . displayChar || char || "" ). trim () || char ;
count += 1 ;
total += value ;
letterParts . push ( ` ${ displayChar } ( ${ value } )` );
return ;
}
const mappedLetters = String ( mappedEntry . mappedLetters || "" );
2026-03-07 05:17:50 -08:00
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-07-06 19:38:49 -07:00
const { cipherEl , inputEl , modeEls , reverseCiphersEl , keyboardScriptEl , keyboardGridEl , keyboardActionsEl } = 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
});
2026-04-13 14:28:03 -07:00
reverseCiphersEl ? . addEventListener ( "change" , ( event ) => {
const target = event . target ;
if ( ! ( target instanceof HTMLInputElement ) || target . type !== "checkbox" ) {
return ;
}
const nextSelectedCipherIds = Array . from ( reverseCiphersEl . querySelectorAll ( "input[type='checkbox']:checked" ))
. map (( element ) => String ( element . value || "" ). trim ())
. filter ( Boolean );
setSelectedReverseCipherIds ( nextSelectedCipherIds );
renderReverseCipherOptions ();
renderGematriaResult ();
});
2026-07-06 19:38:49 -07:00
keyboardScriptEl ? . addEventListener ( "change" , () => {
state . keyboardScriptId = String ( keyboardScriptEl . value || "english" ). trim () || "english" ;
renderKeyboardLayout ();
});
keyboardGridEl ? . addEventListener ( "click" , ( event ) => {
const target = event . target ;
if ( ! ( target instanceof Element )) {
return ;
}
const keyButton = target . closest ( "[data-keyboard-insert]" );
if ( ! ( keyButton instanceof HTMLButtonElement ) || keyButton . disabled ) {
return ;
}
2026-07-11 13:35:38 -07:00
insertGematriaText ( String ( keyButton . dataset . keyboardInsert || "" ), {
focus : false ,
preserveSelection : false
});
2026-07-06 19:38:49 -07:00
});
keyboardActionsEl ? . addEventListener ( "click" , ( event ) => {
const target = event . target ;
if ( ! ( target instanceof Element )) {
return ;
}
const actionButton = target . closest ( "[data-keyboard-action]" );
if ( ! ( actionButton instanceof HTMLButtonElement ) || actionButton . disabled ) {
return ;
}
2026-07-11 13:35:38 -07:00
handleKeyboardAction ( actionButton . dataset . keyboardAction , {
focus : false ,
preserveSelection : false
});
2026-07-06 19:38:49 -07:00
});
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 ();
2026-07-06 19:38:49 -07:00
renderKeyboardLayout ();
2026-03-07 05:17:50 -08:00
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
};
})();