From 9c6438d10ea0364aa05a0fbc6348a2e4f8024f98 Mon Sep 17 00:00:00 2001 From: Nose Date: Mon, 9 Mar 2026 14:43:03 -0700 Subject: [PATCH] anagram and dictionary added --- app/data-service.js | 7 ++ app/styles.css | 22 ++++- app/ui-alphabet-gematria.js | 179 +++++++++++++++++++++++++++++++----- app/ui-alphabet.js | 6 +- index.html | 18 +++- 5 files changed, 202 insertions(+), 30 deletions(-) diff --git a/app/data-service.js b/app/data-service.js index 450d072..23b31e6 100644 --- a/app/data-service.js +++ b/app/data-service.js @@ -372,6 +372,12 @@ })); } + async function loadWordAnagrams(text) { + return fetchJson(buildApiUrl("/api/v1/words/anagrams", { + text + })); + } + async function loadDeckOptions(forceRefresh = false) { if (!forceRefresh && deckOptionsCache) { return deckOptionsCache; @@ -502,6 +508,7 @@ loadDeckManifest, loadDeckOptions, loadGematriaWordsByValue, + loadWordAnagrams, loadQuizCategories, loadQuizTemplates, loadTarotCards, diff --git a/app/styles.css b/app/styles.css index e125a68..365e970 100644 --- a/app/styles.css +++ b/app/styles.css @@ -2326,7 +2326,17 @@ justify-content: flex-end; } - .alpha-gematria-toggle { + .alpha-tool-mode-group { + display: inline-flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + margin: 0; + padding: 0; + border: none; + } + + .alpha-tool-mode-option { display: inline-flex; align-items: center; gap: 8px; @@ -2335,6 +2345,16 @@ cursor: pointer; -webkit-user-select: none; user-select: none; + padding: 6px 10px; + border: 1px solid #3f3f46; + border-radius: 999px; + background: #101018; + } + + .alpha-tool-mode-option:has(input:checked) { + border-color: #6366f1; + background: #1c1b35; + color: #eef2ff; } .alpha-gematria-controls { diff --git a/app/ui-alphabet-gematria.js b/app/ui-alphabet-gematria.js index 0d6ef9d..60196b4 100644 --- a/app/ui-alphabet-gematria.js +++ b/app/ui-alphabet-gematria.js @@ -9,7 +9,7 @@ inputEl: null, resultEl: null, breakdownEl: null, - modeToggleEl: null, + modeEls: [], matchesEl: null, inputLabelEl: null, cipherLabelEl: null @@ -23,10 +23,13 @@ activeCipherId: "", forwardInputText: "", reverseInputText: "", + anagramInputText: "", activeMode: "forward", scriptCharMap: new Map(), reverseLookupCache: new Map(), - reverseRequestId: 0 + anagramLookupCache: new Map(), + reverseRequestId: 0, + anagramRequestId: 0 }; function getAlphabets() { @@ -43,7 +46,7 @@ inputEl: null, resultEl: null, breakdownEl: null, - modeToggleEl: null, + modeEls: [], matchesEl: null, inputLabelEl: null, cipherLabelEl: null @@ -54,10 +57,20 @@ return state.activeMode === "reverse"; } + function isAnagramMode() { + return state.activeMode === "anagram"; + } + function getCurrentInputText() { - return isReverseMode() - ? state.reverseInputText - : state.forwardInputText; + if (isReverseMode()) { + return state.reverseInputText; + } + + if (isAnagramMode()) { + return state.anagramInputText; + } + + return state.forwardInputText; } function formatCount(value) { @@ -295,39 +308,50 @@ matchesEl.hidden = true; } + function getModeElements(modeEls) { + return Array.isArray(modeEls) + ? modeEls.filter((element) => element instanceof HTMLInputElement) + : []; + } + function updateModeUi() { const { cipherEl, inputEl, - modeToggleEl, + modeEls, matchesEl, inputLabelEl, cipherLabelEl } = getElements(); const reverseMode = isReverseMode(); + const anagramMode = isAnagramMode(); + const radioEls = getModeElements(modeEls); - if (modeToggleEl) { - modeToggleEl.checked = reverseMode; - } + radioEls.forEach((element) => { + element.checked = String(element.value || "") === state.activeMode; + }); if (inputLabelEl) { - inputLabelEl.textContent = reverseMode ? "Value" : "Text"; + inputLabelEl.textContent = reverseMode ? "Value" : (anagramMode ? "Letters" : "Text"); } if (cipherLabelEl) { - cipherLabelEl.textContent = reverseMode ? "Cipher (disabled in reverse mode)" : "Cipher"; + cipherLabelEl.textContent = (reverseMode || anagramMode) ? "Cipher (not used in this mode)" : "Cipher"; } if (cipherEl) { - cipherEl.disabled = reverseMode; - cipherEl.closest(".alpha-gematria-field")?.classList.toggle("is-disabled", reverseMode); + const disableCipher = reverseMode || anagramMode; + cipherEl.disabled = disableCipher; + cipherEl.closest(".alpha-gematria-field")?.classList.toggle("is-disabled", disableCipher); } if (inputEl) { - inputEl.placeholder = reverseMode ? "Enter a whole number, e.g. 33" : "Type or paste text"; + inputEl.placeholder = reverseMode + ? "Enter a whole number, e.g. 33" + : (anagramMode ? "Type letters or a word, e.g. listen" : "Type or paste text"); inputEl.inputMode = reverseMode ? "numeric" : "text"; - inputEl.spellcheck = !reverseMode; + inputEl.spellcheck = !(reverseMode || anagramMode); const nextValue = getCurrentInputText(); if (inputEl.value !== nextValue) { @@ -335,7 +359,7 @@ } } - if (!reverseMode) { + if (!reverseMode && !anagramMode) { clearReverseMatches(matchesEl); } } @@ -369,6 +393,17 @@ 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; + } + function renderReverseLookupMatches(payload, numericValue) { const { resultEl, breakdownEl, matchesEl } = getElements(); if (!resultEl || !breakdownEl || !matchesEl) { @@ -489,6 +524,93 @@ } } + 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); + } + + 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."); + } + } + function computeGematria(text, cipher, baseAlphabet) { const normalizedInput = normalizeGematriaText(text); const scriptMap = state.scriptCharMap instanceof Map @@ -569,11 +691,16 @@ return; } + if (isAnagramMode()) { + void renderAnagramResult(); + return; + } + renderForwardGematriaResult(); } function bindGematriaListeners() { - const { cipherEl, inputEl, modeToggleEl } = getElements(); + const { cipherEl, inputEl, modeEls } = getElements(); if (state.listenersBound || !cipherEl || !inputEl) { return; } @@ -586,16 +713,24 @@ inputEl.addEventListener("input", () => { if (isReverseMode()) { state.reverseInputText = inputEl.value || ""; + } else if (isAnagramMode()) { + state.anagramInputText = inputEl.value || ""; } else { state.forwardInputText = inputEl.value || ""; } renderGematriaResult(); }); - modeToggleEl?.addEventListener("change", () => { - state.activeMode = modeToggleEl.checked ? "reverse" : "forward"; - updateModeUi(); - 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; diff --git a/app/ui-alphabet.js b/app/ui-alphabet.js index 32ecda6..1ac9ff2 100644 --- a/app/ui-alphabet.js +++ b/app/ui-alphabet.js @@ -52,7 +52,7 @@ let tabAll, tabHebrew, tabGreek, tabEnglish, tabArabic, tabEnochian; let searchInputEl, searchClearEl, typeFilterEl; let gematriaCipherEl, gematriaInputEl, gematriaResultEl, gematriaBreakdownEl; - let gematriaReverseEl, gematriaMatchesEl, gematriaInputLabelEl, gematriaCipherLabelEl; + let gematriaModeEls, gematriaMatchesEl, gematriaInputLabelEl, gematriaCipherLabelEl; function getElements() { listEl = document.getElementById("alpha-letter-list"); @@ -73,7 +73,7 @@ gematriaInputEl = document.getElementById("alpha-gematria-input"); gematriaResultEl = document.getElementById("alpha-gematria-result"); gematriaBreakdownEl = document.getElementById("alpha-gematria-breakdown"); - gematriaReverseEl = document.getElementById("alpha-gematria-reverse"); + gematriaModeEls = Array.from(document.querySelectorAll("input[name='alpha-tool-mode']")); gematriaMatchesEl = document.getElementById("alpha-gematria-matches"); gematriaInputLabelEl = document.getElementById("alpha-gematria-input-label"); gematriaCipherLabelEl = document.getElementById("alpha-gematria-cipher-label"); @@ -86,7 +86,7 @@ inputEl: gematriaInputEl, resultEl: gematriaResultEl, breakdownEl: gematriaBreakdownEl, - modeToggleEl: gematriaReverseEl, + modeEls: gematriaModeEls, matchesEl: gematriaMatchesEl, inputLabelEl: gematriaInputLabelEl, cipherLabelEl: gematriaCipherLabelEl diff --git a/index.html b/index.html index 2f0b2e1..daa3c7c 100644 --- a/index.html +++ b/index.html @@ -629,10 +629,20 @@
Gematria Lookup
- +
+ + + +