/* ui-alphabet.js — Multi-alphabet browser (English / Hebrew / Greek / Arabic / Enochian) */ (function () { "use strict"; const state = { initialized: false, alphabets: null, activeAlphabet: "all", selectedKey: null, filters: { query: "", letterType: "" }, fourWorldLayers: [], monthRefsByHebrewId: new Map(), cubeRefs: { hebrewPlacementById: new Map(), signPlacementById: new Map(), planetPlacementById: new Map(), pathPlacementByNo: new Map() }, gematria: { loadingPromise: null, db: null, listenersBound: false, activeCipherId: "", inputText: "", scriptCharMap: new Map() } }; // ── Arabic display name table ───────────────────────────────────────── const ARABIC_DISPLAY_NAMES = { alif: "Alif", ba: "Ba", jeem: "Jeem", dal: "Dal", ha: "H\u0101", waw: "W\u0101w", zayn: "Zayn", ha_khaa: "\u1e24\u0101", ta_tay: "\u1e6c\u0101", ya: "Y\u0101", kaf: "K\u0101f", lam: "L\u0101m", meem: "M\u012bm", nun: "N\u016bn", seen: "S\u012bn", ayn: "\u02bfAyn", fa: "F\u0101", sad: "\u1e62\u0101d", qaf: "Q\u0101f", ra: "R\u0101", sheen: "Sh\u012bn", ta: "T\u0101", tha: "Th\u0101", kha: "Kh\u0101", dhal: "Dh\u0101l", dad: "\u1e0c\u0101d", dha: "\u1e92\u0101", ghayn: "Ghayn" }; function arabicDisplayName(letter) { return ARABIC_DISPLAY_NAMES[letter && letter.name] || (String(letter && letter.name || "").charAt(0).toUpperCase() + String(letter && letter.name || "").slice(1)); } // ── Element cache ──────────────────────────────────────────────────── let listEl, countEl, detailNameEl, detailSubEl, detailBodyEl; let tabAll, tabHebrew, tabGreek, tabEnglish, tabArabic, tabEnochian; let searchInputEl, searchClearEl, typeFilterEl; let gematriaCipherEl, gematriaInputEl, gematriaResultEl, gematriaBreakdownEl; function getElements() { listEl = document.getElementById("alpha-letter-list"); countEl = document.getElementById("alpha-letter-count"); detailNameEl = document.getElementById("alpha-detail-name"); detailSubEl = document.getElementById("alpha-detail-sub"); detailBodyEl = document.getElementById("alpha-detail-body"); tabAll = document.getElementById("alpha-tab-all"); tabHebrew = document.getElementById("alpha-tab-hebrew"); tabGreek = document.getElementById("alpha-tab-greek"); tabEnglish = document.getElementById("alpha-tab-english"); tabArabic = document.getElementById("alpha-tab-arabic"); tabEnochian = document.getElementById("alpha-tab-enochian"); searchInputEl = document.getElementById("alpha-search-input"); searchClearEl = document.getElementById("alpha-search-clear"); typeFilterEl = document.getElementById("alpha-type-filter"); gematriaCipherEl = document.getElementById("alpha-gematria-cipher"); gematriaInputEl = document.getElementById("alpha-gematria-input"); gematriaResultEl = document.getElementById("alpha-gematria-result"); gematriaBreakdownEl = document.getElementById("alpha-gematria-breakdown"); } function getFallbackGematriaDb() { return { baseAlphabet: "abcdefghijklmnopqrstuvwxyz", ciphers: [ { id: "simple-ordinal", name: "Simple Ordinal", description: "A=1 ... Z=26", values: Array.from({ length: 26 }, (_, index) => index + 1) } ] }; } function normalizeGematriaText(value) { return String(value || "") .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .toLowerCase(); } function transliterationToBaseLetters(transliteration, baseAlphabet) { const normalized = normalizeGematriaText(transliteration); if (!normalized) { return ""; } const primaryVariant = normalized.split(/[\/,;|]/)[0] || normalized; const primaryLetters = [...primaryVariant].filter((char) => baseAlphabet.includes(char)); if (primaryLetters.length) { return primaryLetters[0]; } const allLetters = [...normalized].filter((char) => baseAlphabet.includes(char)); return allLetters[0] || ""; } function addScriptCharMapEntry(map, scriptChar, mappedLetters) { const key = String(scriptChar || "").trim(); const value = String(mappedLetters || "").trim(); if (!key || !value) { return; } map.set(key, value); } function buildGematriaScriptMap(baseAlphabet) { const map = new Map(); const hebrewLetters = Array.isArray(state.alphabets?.hebrew) ? state.alphabets.hebrew : []; const greekLetters = Array.isArray(state.alphabets?.greek) ? state.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 refreshGematriaScriptMap(baseAlphabet) { state.gematria.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.gematria.db) { return state.gematria.db; } if (state.gematria.loadingPromise) { return state.gematria.loadingPromise; } state.gematria.loadingPromise = fetch("data/gematria-ciphers.json") .then((response) => { if (!response.ok) { throw new Error(`Failed to load gematria ciphers (${response.status})`); } return response.json(); }) .then((db) => { state.gematria.db = sanitizeGematriaDb(db); return state.gematria.db; }) .catch(() => { state.gematria.db = getFallbackGematriaDb(); return state.gematria.db; }) .finally(() => { state.gematria.loadingPromise = null; }); return state.gematria.loadingPromise; } function getActiveGematriaCipher() { const db = state.gematria.db || getFallbackGematriaDb(); const ciphers = Array.isArray(db.ciphers) ? db.ciphers : []; if (!ciphers.length) { return null; } const selectedId = state.gematria.activeCipherId || ciphers[0].id; return ciphers.find((cipher) => cipher.id === selectedId) || ciphers[0]; } function renderGematriaCipherOptions() { if (!gematriaCipherEl) { return; } const db = state.gematria.db || getFallbackGematriaDb(); const ciphers = Array.isArray(db.ciphers) ? db.ciphers : []; gematriaCipherEl.innerHTML = ""; ciphers.forEach((cipher) => { const option = document.createElement("option"); option.value = cipher.id; option.textContent = cipher.name; if (cipher.description) { option.title = cipher.description; } gematriaCipherEl.appendChild(option); }); const activeCipher = getActiveGematriaCipher(); state.gematria.activeCipherId = activeCipher?.id || ""; gematriaCipherEl.value = state.gematria.activeCipherId; } function computeGematria(text, cipher, baseAlphabet) { const normalizedInput = normalizeGematriaText(text); const scriptMap = state.gematria.scriptCharMap instanceof Map ? state.gematria.scriptCharMap : new Map(); const letterParts = []; let total = 0; let count = 0; [...normalizedInput].forEach((char) => { const mappedLetters = baseAlphabet.includes(char) ? char : (scriptMap.get(char) || ""); if (!mappedLetters) { return; } [...mappedLetters].forEach((mappedChar) => { const index = baseAlphabet.indexOf(mappedChar); if (index < 0) { return; } const value = Number(cipher.values[index]); if (!Number.isFinite(value)) { return; } count += 1; total += value; letterParts.push(`${mappedChar.toUpperCase()}(${value})`); }); }); return { total, count, breakdown: letterParts.join(" + ") }; } function renderGematriaResult() { if (!gematriaResultEl || !gematriaBreakdownEl) { return; } const db = state.gematria.db || getFallbackGematriaDb(); if (!(state.gematria.scriptCharMap instanceof Map) || !state.gematria.scriptCharMap.size) { refreshGematriaScriptMap(db.baseAlphabet); } const cipher = getActiveGematriaCipher(); if (!cipher) { gematriaResultEl.textContent = "Total: --"; gematriaBreakdownEl.textContent = "No ciphers available."; return; } const { total, count, breakdown } = computeGematria(state.gematria.inputText, cipher, db.baseAlphabet); gematriaResultEl.textContent = `Total: ${total}`; if (!count) { gematriaBreakdownEl.textContent = `Using ${cipher.name}. Enter English, Greek, or Hebrew letters to calculate.`; return; } gematriaBreakdownEl.textContent = `${cipher.name} · ${count} letters · ${breakdown} = ${total}`; } function bindGematriaListeners() { if (state.gematria.listenersBound || !gematriaCipherEl || !gematriaInputEl) { return; } gematriaCipherEl.addEventListener("change", () => { state.gematria.activeCipherId = String(gematriaCipherEl.value || "").trim(); renderGematriaResult(); }); gematriaInputEl.addEventListener("input", () => { state.gematria.inputText = gematriaInputEl.value || ""; renderGematriaResult(); }); state.gematria.listenersBound = true; } function ensureGematriaCalculator() { getElements(); if (!gematriaCipherEl || !gematriaInputEl || !gematriaResultEl || !gematriaBreakdownEl) { return; } bindGematriaListeners(); if (gematriaInputEl.value !== state.gematria.inputText) { gematriaInputEl.value = state.gematria.inputText; } void loadGematriaDb().then(() => { refreshGematriaScriptMap((state.gematria.db || getFallbackGematriaDb()).baseAlphabet); renderGematriaCipherOptions(); renderGematriaResult(); }); } // ── Data helpers ───────────────────────────────────────────────────── function getLetters() { if (!state.alphabets) return []; if (state.activeAlphabet === "all") { const alphabetOrder = ["hebrew", "greek", "english", "arabic", "enochian"]; return alphabetOrder.flatMap((alphabet) => { const rows = Array.isArray(state.alphabets?.[alphabet]) ? state.alphabets[alphabet] : []; return rows.map((row) => ({ ...row, __alphabet: alphabet })); }); } return state.alphabets[state.activeAlphabet] || []; } function alphabetForLetter(letter) { if (state.activeAlphabet === "all") { return String(letter?.__alphabet || "").trim().toLowerCase(); } return state.activeAlphabet; } function letterKeyByAlphabet(alphabet, letter) { if (alphabet === "hebrew") return letter.hebrewLetterId; if (alphabet === "greek") return letter.name; if (alphabet === "english") return letter.letter; if (alphabet === "arabic") return letter.name; if (alphabet === "enochian") return letter.id; return String(letter.index); } function letterKey(letter) { // Stable unique key per alphabet + entry const alphabet = alphabetForLetter(letter); const key = letterKeyByAlphabet(alphabet, letter); if (state.activeAlphabet === "all") { return `${alphabet}:${key}`; } return key; } function displayGlyph(letter) { const alphabet = alphabetForLetter(letter); if (alphabet === "hebrew") return letter.char; if (alphabet === "greek") return letter.char; if (alphabet === "english") return letter.letter; if (alphabet === "arabic") return letter.char; if (alphabet === "enochian") return letter.char; return "?"; } function displayLabel(letter) { const alphabet = alphabetForLetter(letter); if (alphabet === "hebrew") return letter.name; if (alphabet === "greek") return letter.displayName; if (alphabet === "english") return letter.letter; if (alphabet === "arabic") return arabicDisplayName(letter); if (alphabet === "enochian") return letter.title; return "?"; } function displaySub(letter) { const alphabet = alphabetForLetter(letter); if (alphabet === "hebrew") return `${letter.transliteration} · ${letter.letterType} · ${letter.numerology}`; if (alphabet === "greek") return `${letter.transliteration} · isopsephy ${letter.numerology}${letter.archaic ? " · archaic" : ""}`; if (alphabet === "english") return `Pythagorean ${letter.pythagorean}`; if (alphabet === "arabic") return `${letter.transliteration} · abjad ${letter.abjad} · ${letter.nameArabic}`; if (alphabet === "enochian") return `${letter.transliteration} · ${Array.isArray(letter.englishLetters) ? letter.englishLetters.join("/") : ""} · value ${letter.numerology}`; return ""; } function normalizeLetterType(value) { const key = String(value || "").trim().toLowerCase(); if (["mother", "double", "simple"].includes(key)) { return key; } return ""; } function getHebrewLetterTypeMap() { const map = new Map(); const hebrewLetters = Array.isArray(state.alphabets?.hebrew) ? state.alphabets.hebrew : []; hebrewLetters.forEach((entry) => { const hebrewId = normalizeId(entry?.hebrewLetterId); const letterType = normalizeLetterType(entry?.letterType); if (hebrewId && letterType) { map.set(hebrewId, letterType); } }); return map; } function resolveLetterType(letter) { const direct = normalizeLetterType(letter?.letterType); if (direct) { return direct; } const hebrewId = normalizeId(letter?.hebrewLetterId); if (!hebrewId) { return ""; } return getHebrewLetterTypeMap().get(hebrewId) || ""; } function buildLetterSearchText(letter) { const chunks = []; chunks.push(String(displayLabel(letter) || "")); chunks.push(String(displayGlyph(letter) || "")); chunks.push(String(displaySub(letter) || "")); chunks.push(String(letter?.transliteration || "")); chunks.push(String(letter?.meaning || "")); chunks.push(String(letter?.nameArabic || "")); chunks.push(String(letter?.title || "")); chunks.push(String(letter?.letter || "")); chunks.push(String(letter?.displayName || "")); chunks.push(String(letter?.name || "")); chunks.push(String(letter?.index || "")); chunks.push(String(resolveLetterType(letter) || "")); return chunks .join(" ") .toLowerCase(); } function getFilteredLetters() { const letters = getLetters(); const query = String(state.filters.query || "").trim().toLowerCase(); const letterTypeFilter = normalizeLetterType(state.filters.letterType); const numericPosition = /^\d+$/.test(query) ? Number(query) : null; return letters.filter((letter) => { if (letterTypeFilter) { const entryType = resolveLetterType(letter); if (entryType !== letterTypeFilter) { return false; } } if (!query) { return true; } const index = Number(letter?.index); if (Number.isFinite(numericPosition) && Number.isFinite(index) && index === numericPosition) { return true; } return buildLetterSearchText(letter).includes(query); }); } function syncFilterControls() { if (searchInputEl && searchInputEl.value !== state.filters.query) { searchInputEl.value = state.filters.query; } if (typeFilterEl && typeFilterEl.value !== state.filters.letterType) { typeFilterEl.value = state.filters.letterType; } if (searchClearEl) { const hasFilter = Boolean(String(state.filters.query || "").trim()) || Boolean(state.filters.letterType); searchClearEl.disabled = !hasFilter; } } function applyFiltersAndRender() { const filteredLetters = getFilteredLetters(); const selectedInFiltered = filteredLetters.some((letter) => letterKey(letter) === state.selectedKey); if (!selectedInFiltered) { state.selectedKey = filteredLetters[0] ? letterKey(filteredLetters[0]) : null; } renderList(); const selected = filteredLetters.find((letter) => letterKey(letter) === state.selectedKey) || filteredLetters[0] || null; if (selected) { renderDetail(selected); return; } resetDetail(); if (detailSubEl) { detailSubEl.textContent = "No letters match the current filter."; } } function bindFilterControls() { if (searchInputEl) { searchInputEl.addEventListener("input", () => { state.filters.query = String(searchInputEl.value || ""); syncFilterControls(); applyFiltersAndRender(); }); } if (typeFilterEl) { typeFilterEl.addEventListener("change", () => { state.filters.letterType = normalizeLetterType(typeFilterEl.value); syncFilterControls(); applyFiltersAndRender(); }); } if (searchClearEl) { searchClearEl.addEventListener("click", () => { state.filters.query = ""; state.filters.letterType = ""; syncFilterControls(); applyFiltersAndRender(); }); } } function enochianGlyphKey(letter) { return String(letter?.id || letter?.char || "").trim().toUpperCase(); } function enochianGlyphCode(letter) { const key = enochianGlyphKey(letter); return key ? key.codePointAt(0) || 0 : 0; } function enochianGlyphUrl(letter) { const code = enochianGlyphCode(letter); return code ? `asset/img/enochian/char(${code}).png` : ""; } function enochianGlyphImageHtml(letter, className) { const src = enochianGlyphUrl(letter); const key = enochianGlyphKey(letter) || "?"; if (!src) { return `${key}`; } return `Enochian ${key}`; } // ── List rendering ──────────────────────────────────────────────────── function renderList() { if (!listEl) return; const allLetters = getLetters(); const letters = getFilteredLetters(); if (countEl) { countEl.textContent = letters.length === allLetters.length ? `${letters.length}` : `${letters.length}/${allLetters.length}`; } listEl.innerHTML = ""; letters.forEach((letter) => { const key = letterKey(letter); const item = document.createElement("div"); item.className = "planet-list-item alpha-list-item" + (key === state.selectedKey ? " is-selected" : ""); item.setAttribute("role", "option"); item.setAttribute("aria-selected", key === state.selectedKey ? "true" : "false"); item.dataset.key = key; const glyph = document.createElement("span"); const alphabet = alphabetForLetter(letter); const glyphVariantClass = alphabet === "arabic" ? " alpha-list-glyph--arabic" : alphabet === "enochian" ? " alpha-list-glyph--enochian" : ""; glyph.className = "alpha-list-glyph" + glyphVariantClass; if (alphabet === "enochian") { const image = document.createElement("img"); image.className = "alpha-enochian-glyph-img alpha-enochian-glyph-img--list"; image.src = enochianGlyphUrl(letter); image.alt = `Enochian ${enochianGlyphKey(letter) || "?"}`; image.loading = "lazy"; image.decoding = "async"; image.addEventListener("error", () => { glyph.textContent = enochianGlyphKey(letter) || "?"; }); glyph.appendChild(image); } else { glyph.textContent = displayGlyph(letter); } const meta = document.createElement("span"); meta.className = "alpha-list-meta"; const alphaLabel = alphabet ? `${cap(alphabet)} · ` : ""; meta.innerHTML = `${alphaLabel}${displayLabel(letter)}
${displaySub(letter)}`; item.appendChild(glyph); item.appendChild(meta); item.addEventListener("click", () => selectLetter(key)); listEl.appendChild(item); }); } // ── Detail rendering ────────────────────────────────────────────────── function renderDetail(letter) { if (!detailNameEl) return; const alphabet = alphabetForLetter(letter); detailNameEl.replaceChildren(); if (alphabet === "enochian") { const image = document.createElement("img"); image.className = "alpha-enochian-glyph-img alpha-enochian-glyph-img--detail"; image.src = enochianGlyphUrl(letter); image.alt = `Enochian ${enochianGlyphKey(letter) || "?"}`; image.loading = "lazy"; image.decoding = "async"; image.addEventListener("error", () => { detailNameEl.textContent = enochianGlyphKey(letter) || "?"; }); detailNameEl.appendChild(image); } else { detailNameEl.textContent = displayGlyph(letter); } detailNameEl.classList.add("alpha-detail-glyph"); detailNameEl.classList.toggle("alpha-detail-glyph--arabic", alphabet === "arabic"); detailNameEl.classList.toggle("alpha-detail-glyph--enochian", alphabet === "enochian"); if (alphabet === "hebrew") renderHebrewDetail(letter); else if (alphabet === "greek") renderGreekDetail(letter); else if (alphabet === "english") renderEnglishDetail(letter); else if (alphabet === "arabic") renderArabicDetail(letter); else if (alphabet === "enochian") renderEnochianDetail(letter); } function card(title, bodyHTML) { return `
${title}
${bodyHTML}
`; } const PLANET_SYMBOLS = { mercury: "☿︎", luna: "☾︎", venus: "♀︎", sol: "☉︎", jupiter: "♃︎", mars: "♂︎", saturn: "♄︎" }; const ZODIAC_SYMBOLS = { aries: "♈︎", taurus: "♉︎", gemini: "♊︎", cancer: "♋︎", leo: "♌︎", virgo: "♍︎", libra: "♎︎", scorpio: "♏︎", sagittarius: "♐︎", capricorn: "♑︎", aquarius: "♒︎", pisces: "♓︎" }; const HEBREW_DOUBLE_DUALITY = { bet: { left: "Life", right: "Death" }, gimel: { left: "Peace", right: "War" }, dalet: { left: "Wisdom", right: "Folly" }, kaf: { left: "Wealth", right: "Poverty" }, pe: { left: "Beauty", right: "Ugliness" }, resh: { left: "Fruitfulness", right: "Sterility" }, tav: { left: "Dominion", right: "Slavery" } }; function normalizeId(value) { return String(value || "").trim().toLowerCase(); } function normalizeSoulId(value) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z]/g, ""); } function buildFourWorldLayersFromDataset(magickDataset) { const worlds = magickDataset?.grouped?.kabbalah?.fourWorlds; const souls = magickDataset?.grouped?.kabbalah?.souls; const paths = Array.isArray(magickDataset?.grouped?.kabbalah?.["kabbalah-tree"]?.paths) ? magickDataset.grouped.kabbalah["kabbalah-tree"].paths : []; if (!worlds || typeof worlds !== "object") { return []; } const soulAliases = { chiah: "chaya", chaya: "chaya", neshamah: "neshama", neshama: "neshama", ruach: "ruach", nephesh: "nephesh" }; const pathByLetterId = new Map(); paths.forEach((path) => { const letterId = normalizeLetterId(path?.hebrewLetter?.transliteration || path?.hebrewLetter?.char); const pathNo = Number(path?.pathNumber); if (!letterId || !Number.isFinite(pathNo) || pathByLetterId.has(letterId)) { return; } pathByLetterId.set(letterId, pathNo); }); const worldOrder = ["atzilut", "briah", "yetzirah", "assiah"]; return worldOrder .map((worldId) => { const world = worlds?.[worldId]; if (!world || typeof world !== "object") { return null; } const tetragrammaton = world?.tetragrammaton && typeof world.tetragrammaton === "object" ? world.tetragrammaton : {}; const letterId = normalizeLetterId(tetragrammaton?.hebrewLetterId); const rawSoulId = normalizeSoulId(world?.soulId); const soulId = soulAliases[rawSoulId] || rawSoulId; const soul = souls?.[soulId] && typeof souls[soulId] === "object" ? souls[soulId] : null; const slot = tetragrammaton?.isFinal ? `${String(tetragrammaton?.slot || "Heh")} (final)` : String(tetragrammaton?.slot || ""); return { slot, letterChar: String(tetragrammaton?.letterChar || ""), hebrewLetterId: letterId, world: String(world?.name?.roman || titleCase(worldId)), worldLayer: String(world?.worldLayer?.en || world?.desc?.en || ""), worldDescription: String(world?.worldDescription?.en || ""), soulLayer: String(soul?.name?.roman || titleCase(rawSoulId || soulId)), soulTitle: String(soul?.title?.en || titleCase(soul?.name?.en || "")), soulDescription: String(soul?.desc?.en || ""), pathNumber: pathByLetterId.get(letterId) || null }; }) .filter(Boolean); } function buildMonthReferencesByHebrew(referenceData, alphabets) { const map = new Map(); const months = Array.isArray(referenceData?.calendarMonths) ? referenceData.calendarMonths : []; const holidays = Array.isArray(referenceData?.celestialHolidays) ? referenceData.celestialHolidays : []; const monthById = new Map(months.map((month) => [month.id, month])); const hebrewLetters = Array.isArray(alphabets?.hebrew) ? alphabets.hebrew : []; const profiles = hebrewLetters .filter((letter) => letter?.hebrewLetterId) .map((letter) => { const astrologyType = normalizeId(letter?.astrology?.type); const astrologyName = normalizeId(letter?.astrology?.name); return { hebrewLetterId: normalizeId(letter.hebrewLetterId), tarotTrumpNumber: Number.isFinite(Number(letter?.tarot?.trumpNumber)) ? Number(letter.tarot.trumpNumber) : null, kabbalahPathNumber: Number.isFinite(Number(letter?.kabbalahPathNumber)) ? Number(letter.kabbalahPathNumber) : null, planetId: astrologyType === "planet" ? astrologyName : "", zodiacSignId: astrologyType === "zodiac" ? astrologyName : "" }; }); function parseMonthDayToken(value) { const text = String(value || "").trim(); const match = text.match(/^(\d{1,2})-(\d{1,2})$/); if (!match) { return null; } const monthNo = Number(match[1]); const dayNo = Number(match[2]); if (!Number.isInteger(monthNo) || !Number.isInteger(dayNo) || monthNo < 1 || monthNo > 12 || dayNo < 1 || dayNo > 31) { return null; } return { month: monthNo, day: dayNo }; } function parseMonthDayTokensFromText(value) { const text = String(value || ""); const matches = [...text.matchAll(/(\d{1,2})-(\d{1,2})/g)]; return matches .map((match) => ({ month: Number(match[1]), day: Number(match[2]) })) .filter((token) => Number.isInteger(token.month) && Number.isInteger(token.day) && token.month >= 1 && token.month <= 12 && token.day >= 1 && token.day <= 31); } function toDateToken(token, year) { if (!token) { return null; } return new Date(year, token.month - 1, token.day, 12, 0, 0, 0); } function splitMonthDayRangeByMonth(startToken, endToken) { const startDate = toDateToken(startToken, 2025); const endBase = toDateToken(endToken, 2025); if (!startDate || !endBase) { return []; } const wrapsYear = endBase.getTime() < startDate.getTime(); const endDate = wrapsYear ? toDateToken(endToken, 2026) : endBase; if (!endDate) { return []; } const segments = []; let cursor = new Date(startDate); while (cursor.getTime() <= endDate.getTime()) { const monthEnd = new Date(cursor.getFullYear(), cursor.getMonth() + 1, 0, 12, 0, 0, 0); const segmentEnd = monthEnd.getTime() < endDate.getTime() ? monthEnd : endDate; segments.push({ monthNo: cursor.getMonth() + 1, startDay: cursor.getDate(), endDay: segmentEnd.getDate() }); cursor = new Date(segmentEnd.getFullYear(), segmentEnd.getMonth(), segmentEnd.getDate() + 1, 12, 0, 0, 0); } return segments; } function tokenToString(monthNo, dayNo) { return `${String(monthNo).padStart(2, "0")}-${String(dayNo).padStart(2, "0")}`; } function formatRangeLabel(monthName, startDay, endDay) { if (!Number.isFinite(startDay) || !Number.isFinite(endDay)) { return monthName; } if (startDay === endDay) { return `${monthName} ${startDay}`; } return `${monthName} ${startDay}-${endDay}`; } function resolveRangeForMonth(month, options = {}) { const monthOrder = Number(month?.order); const monthStart = parseMonthDayToken(month?.start); const monthEnd = parseMonthDayToken(month?.end); if (!Number.isFinite(monthOrder) || !monthStart || !monthEnd) { return { startToken: String(month?.start || "").trim() || null, endToken: String(month?.end || "").trim() || null, label: month?.name || month?.id || "", isFullMonth: true }; } let startToken = parseMonthDayToken(options.startToken); let endToken = parseMonthDayToken(options.endToken); if (!startToken || !endToken) { const tokens = parseMonthDayTokensFromText(options.rawDateText); if (tokens.length >= 2) { startToken = tokens[0]; endToken = tokens[1]; } else if (tokens.length === 1) { startToken = tokens[0]; endToken = tokens[0]; } } if (!startToken || !endToken) { startToken = monthStart; endToken = monthEnd; } const segments = splitMonthDayRangeByMonth(startToken, endToken); const segment = segments.find((entry) => entry.monthNo === monthOrder) || null; const useStart = segment ? { month: monthOrder, day: segment.startDay } : startToken; const useEnd = segment ? { month: monthOrder, day: segment.endDay } : endToken; const startText = tokenToString(useStart.month, useStart.day); const endText = tokenToString(useEnd.month, useEnd.day); const isFullMonth = startText === month.start && endText === month.end; return { startToken: startText, endToken: endText, label: isFullMonth ? (month.name || month.id) : formatRangeLabel(month.name || month.id, useStart.day, useEnd.day), isFullMonth }; } function pushRef(hebrewLetterId, month, options = {}) { if (!hebrewLetterId || !month?.id) { return; } if (!map.has(hebrewLetterId)) { map.set(hebrewLetterId, []); } const rows = map.get(hebrewLetterId); const range = resolveRangeForMonth(month, options); const rowKey = `${month.id}|${range.startToken || ""}|${range.endToken || ""}`; if (rows.some((entry) => entry.key === rowKey)) { return; } rows.push({ id: month.id, name: month.name || month.id, order: Number.isFinite(Number(month.order)) ? Number(month.order) : 999, label: range.label, startToken: range.startToken, endToken: range.endToken, isFullMonth: range.isFullMonth, key: rowKey }); } function collectRefs(associations, month, options = {}) { if (!associations || typeof associations !== "object") { return; } const assocHebrewId = normalizeId(associations.hebrewLetterId); const assocTarotTrump = Number.isFinite(Number(associations.tarotTrumpNumber)) ? Number(associations.tarotTrumpNumber) : null; const assocPath = Number.isFinite(Number(associations.kabbalahPathNumber)) ? Number(associations.kabbalahPathNumber) : null; const assocPlanetId = normalizeId(associations.planetId); const assocSignId = normalizeId(associations.zodiacSignId); profiles.forEach((profile) => { if (!profile.hebrewLetterId) { return; } const matchesDirect = assocHebrewId && assocHebrewId === profile.hebrewLetterId; const matchesTarot = assocTarotTrump != null && profile.tarotTrumpNumber === assocTarotTrump; const matchesPath = assocPath != null && profile.kabbalahPathNumber === assocPath; const matchesPlanet = profile.planetId && assocPlanetId && profile.planetId === assocPlanetId; const matchesZodiac = profile.zodiacSignId && assocSignId && profile.zodiacSignId === assocSignId; if (matchesDirect || matchesTarot || matchesPath || matchesPlanet || matchesZodiac) { pushRef(profile.hebrewLetterId, month, options); } }); } months.forEach((month) => { collectRefs(month?.associations, month); const events = Array.isArray(month?.events) ? month.events : []; events.forEach((event) => { collectRefs(event?.associations, month, { rawDateText: event?.dateRange || event?.date || "" }); }); }); holidays.forEach((holiday) => { const month = monthById.get(holiday?.monthId); if (!month) { return; } collectRefs(holiday?.associations, month, { rawDateText: holiday?.dateRange || holiday?.date || "" }); }); map.forEach((rows, key) => { const preciseMonthIds = new Set( rows .filter((entry) => !entry.isFullMonth) .map((entry) => entry.id) ); const filtered = rows.filter((entry) => { if (!entry.isFullMonth) { return true; } return !preciseMonthIds.has(entry.id); }); filtered.sort((left, right) => { if (left.order !== right.order) { return left.order - right.order; } const startLeft = parseMonthDayToken(left.startToken); const startRight = parseMonthDayToken(right.startToken); const dayLeft = startLeft ? startLeft.day : 999; const dayRight = startRight ? startRight.day : 999; if (dayLeft !== dayRight) { return dayLeft - dayRight; } return String(left.label || left.name || "").localeCompare(String(right.label || right.name || "")); }); map.set(key, filtered); }); return map; } function createEmptyCubeRefs() { return { hebrewPlacementById: new Map(), signPlacementById: new Map(), planetPlacementById: new Map(), pathPlacementByNo: new Map() }; } function normalizeLetterId(value) { const key = normalizeId(value).replace(/[^a-z]/g, ""); const aliases = { aleph: "alef", beth: "bet", zain: "zayin", cheth: "het", chet: "het", daleth: "dalet", teth: "tet", peh: "pe", tzaddi: "tsadi", tzadi: "tsadi", tzade: "tsadi", tsaddi: "tsadi", qoph: "qof", taw: "tav", tau: "tav" }; return aliases[key] || key; } function edgeWalls(edge) { const explicitWalls = Array.isArray(edge?.walls) ? edge.walls.map((wallId) => normalizeId(wallId)).filter(Boolean) : []; if (explicitWalls.length >= 2) { return explicitWalls.slice(0, 2); } return normalizeId(edge?.id) .split("-") .map((wallId) => normalizeId(wallId)) .filter(Boolean) .slice(0, 2); } function edgeLabel(edge) { const explicitName = String(edge?.name || "").trim(); if (explicitName) { return explicitName; } return edgeWalls(edge) .map((part) => cap(part)) .join(" "); } function resolveCubeDirectionLabel(wallId, edge) { const normalizedWallId = normalizeId(wallId); const edgeId = normalizeId(edge?.id); if (!normalizedWallId || !edgeId) { return ""; } const cubeUi = window.CubeSectionUi; if (cubeUi && typeof cubeUi.getEdgeDirectionLabelForWall === "function") { const directionLabel = String(cubeUi.getEdgeDirectionLabelForWall(normalizedWallId, edgeId) || "").trim(); if (directionLabel) { return directionLabel; } } return edgeLabel(edge); } function makeCubePlacement(wall, edge = null) { const wallId = normalizeId(wall?.id); const edgeId = normalizeId(edge?.id); return { wallId, edgeId, wallName: wall?.name || cap(wallId), edgeName: resolveCubeDirectionLabel(wallId, edge) }; } function setPlacementIfMissing(map, key, placement) { if (!key || map.has(key) || !placement?.wallId) { return; } map.set(key, placement); } function buildCubeReferences(magickDataset) { const refs = createEmptyCubeRefs(); const cube = magickDataset?.grouped?.kabbalah?.cube || {}; const walls = Array.isArray(cube?.walls) ? cube.walls : []; const edges = Array.isArray(cube?.edges) ? cube.edges : []; const paths = Array.isArray(magickDataset?.grouped?.kabbalah?.["kabbalah-tree"]?.paths) ? magickDataset.grouped.kabbalah["kabbalah-tree"].paths : []; const wallById = new Map( walls.map((wall) => [normalizeId(wall?.id), wall]) ); const firstEdgeByWallId = new Map(); const pathByLetterId = new Map( paths .map((path) => [normalizeLetterId(path?.hebrewLetter?.transliteration), path]) .filter(([letterId]) => Boolean(letterId)) ); edges.forEach((edge) => { edgeWalls(edge).forEach((wallId) => { if (!firstEdgeByWallId.has(wallId)) { firstEdgeByWallId.set(wallId, edge); } }); }); walls.forEach((wall) => { // each wall has a "face" letter; when we build a cube reference for that // letter we want the label to read “Face” rather than arbitrarily using // the first edge we encounter on that wall. previously we always // computed `placementEdge` from the first edge, which produced labels // like “East Wall – North” for the dalet face. instead we create a // custom placement object for face letters with an empty edge id and a // fixed edgeName of “Face”. const wallHebrewLetterId = normalizeLetterId(wall?.hebrewLetterId || wall?.associations?.hebrewLetterId); let wallPlacement; if (wallHebrewLetterId) { // face letter; label should emphasise the face rather than a direction wallPlacement = { wallId: normalizeId(wall?.id), edgeId: "", wallName: wall?.name || cap(normalizeId(wall?.id)), edgeName: "Face" }; } else { // fall back to normal edge-based placement const placementEdge = firstEdgeByWallId.get(normalizeId(wall?.id)) || null; wallPlacement = makeCubePlacement(wall, placementEdge); } setPlacementIfMissing(refs.hebrewPlacementById, wallHebrewLetterId, wallPlacement); const wallPath = pathByLetterId.get(wallHebrewLetterId) || null; const wallSignId = normalizeId(wallPath?.astrology?.type) === "zodiac" ? normalizeId(wallPath?.astrology?.name) : ""; setPlacementIfMissing(refs.signPlacementById, wallSignId, wallPlacement); const wallPathNo = Number(wallPath?.pathNumber); if (Number.isFinite(wallPathNo)) { setPlacementIfMissing(refs.pathPlacementByNo, wallPathNo, wallPlacement); } const wallPlanet = normalizeId(wall?.associations?.planetId); if (wallPlanet) { setPlacementIfMissing(refs.planetPlacementById, wallPlanet, wallPlacement); } }); edges.forEach((edge) => { const wallsForEdge = edgeWalls(edge); const primaryWallId = wallsForEdge[0]; const primaryWall = wallById.get(primaryWallId) || { id: primaryWallId, name: cap(primaryWallId) }; const placement = makeCubePlacement(primaryWall, edge); const hebrewLetterId = normalizeLetterId(edge?.hebrewLetterId || edge?.associations?.hebrewLetterId); setPlacementIfMissing(refs.hebrewPlacementById, hebrewLetterId, placement); const path = pathByLetterId.get(hebrewLetterId) || null; const signId = normalizeId(path?.astrology?.type) === "zodiac" ? normalizeId(path?.astrology?.name) : ""; setPlacementIfMissing(refs.signPlacementById, signId, placement); const pathNo = Number(path?.pathNumber); if (Number.isFinite(pathNo)) { setPlacementIfMissing(refs.pathPlacementByNo, pathNo, placement); } }); return refs; } function getCubePlacementForHebrewLetter(hebrewLetterId, pathNo = null) { const normalizedLetterId = normalizeId(hebrewLetterId); if (normalizedLetterId && state.cubeRefs.hebrewPlacementById.has(normalizedLetterId)) { return state.cubeRefs.hebrewPlacementById.get(normalizedLetterId); } const numericPath = Number(pathNo); if (Number.isFinite(numericPath) && state.cubeRefs.pathPlacementByNo.has(numericPath)) { return state.cubeRefs.pathPlacementByNo.get(numericPath); } return null; } function getCubePlacementForPlanet(planetId) { const normalizedPlanetId = normalizeId(planetId); return normalizedPlanetId ? state.cubeRefs.planetPlacementById.get(normalizedPlanetId) || null : null; } function getCubePlacementForSign(signId) { const normalizedSignId = normalizeId(signId); return normalizedSignId ? state.cubeRefs.signPlacementById.get(normalizedSignId) || null : null; } function cubePlacementLabel(placement) { const wallName = placement?.wallName || "Wall"; const edgeName = placement?.edgeName || "Direction"; return `Cube: ${wallName} Wall - ${edgeName}`; } function cubePlacementBtn(placement, fallbackDetail = null) { if (!placement) { return ""; } const detail = { "wall-id": placement.wallId, "edge-id": placement.edgeId }; if (fallbackDetail && typeof fallbackDetail === "object") { Object.entries(fallbackDetail).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== "") { detail[key] = value; } }); } return navBtn(cubePlacementLabel(placement), "nav:cube", detail); } function cap(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : ""; } function renderAstrologyCard(astrology) { if (!astrology) return ""; const { type, name } = astrology; const id = (name || "").toLowerCase(); if (type === "planet") { const sym = PLANET_SYMBOLS[id] || ""; const cubePlacement = getCubePlacementForPlanet(id); const cubeBtn = cubePlacementBtn(cubePlacement, { "planet-id": id }); return card("Astrology", `
Type
Planet
Ruler
${sym} ${cap(id)}
${cubeBtn}
`); } if (type === "zodiac") { const sym = ZODIAC_SYMBOLS[id] || ""; const cubePlacement = getCubePlacementForSign(id); const cubeBtn = cubePlacementBtn(cubePlacement, { "sign-id": id }); return card("Astrology", `
Type
Zodiac Sign
Sign
${sym} ${cap(id)}
${cubeBtn}
`); } if (type === "element") { const elemEmoji = { air: "💨", water: "💧", fire: "🔥", earth: "🌍" }; return card("Astrology", `
Type
Element
Element
${elemEmoji[id] || ""} ${cap(id)}
`); } return card("Astrology", `
Type
${cap(type)}
Name
${cap(name)}
`); } function navBtn(label, event, detail) { const attrs = Object.entries(detail).map(([k, v]) => `data-${k}="${v}"`).join(" "); return ``; } function computeDigitalRoot(value) { let current = Math.abs(Math.trunc(Number(value))); if (!Number.isFinite(current)) { return null; } while (current >= 10) { current = String(current) .split("") .reduce((sum, digit) => sum + Number(digit), 0); } return current; } function describeDigitalRootReduction(value, digitalRoot) { const normalized = Math.abs(Math.trunc(Number(value))); if (!Number.isFinite(normalized) || !Number.isFinite(digitalRoot)) { return ""; } if (normalized < 10) { return String(normalized); } return `${String(normalized).split("").join(" + ")} = ${digitalRoot}`; } function renderPositionDigitalRootCard(letter, alphabet, orderLabel) { const index = Number(letter?.index); if (!Number.isFinite(index)) { return ""; } const position = Math.trunc(index); if (position <= 0) { return ""; } const digitalRoot = computeDigitalRoot(position); if (!Number.isFinite(digitalRoot)) { return ""; } const entries = Array.isArray(state.alphabets?.[alphabet]) ? state.alphabets[alphabet] : []; const countText = entries.length ? ` of ${entries.length}` : ""; const orderText = orderLabel ? ` (${orderLabel})` : ""; const reductionText = describeDigitalRootReduction(position, digitalRoot); const openNumberBtn = navBtn(`View Number ${digitalRoot}`, "nav:number", { value: digitalRoot }); return card("Position Digital Root", `
Position
#${position}${countText}${orderText}
Digital Root
${digitalRoot}${reductionText ? ` (${reductionText})` : ""}
${openNumberBtn}
`); } function monthRefsForLetter(letter) { const hebrewLetterId = normalizeId(letter?.hebrewLetterId); if (!hebrewLetterId) { return []; } return state.monthRefsByHebrewId.get(hebrewLetterId) || []; } function calendarMonthsCard(monthRefs, titleLabel) { if (!monthRefs.length) { return ""; } const monthButtons = monthRefs .map((month) => navBtn(month.label || month.name, "nav:calendar-month", { "month-id": month.id })) .join(""); return card("Calendar Months", `
${titleLabel}
${monthButtons}
`); } function renderHebrewDualityCard(letter) { const duality = HEBREW_DOUBLE_DUALITY[normalizeId(letter?.hebrewLetterId)]; if (!duality) { return ""; } return card("Duality", `
Polarity
${duality.left} / ${duality.right}
`); } function renderHebrewFourWorldsCard(letter) { const letterId = normalizeLetterId(letter?.hebrewLetterId || letter?.transliteration || letter?.char); if (!letterId) { return ""; } const rows = (Array.isArray(state.fourWorldLayers) ? state.fourWorldLayers : []) .filter((entry) => entry?.hebrewLetterId === letterId); if (!rows.length) { return ""; } const body = rows.map((entry) => { const pathBtn = Number.isFinite(Number(entry?.pathNumber)) ? navBtn(`View Path ${entry.pathNumber}`, "nav:kabbalah-path", { "path-no": Number(entry.pathNumber) }) : ""; return `
${entry.slot}: ${entry.letterChar} — ${entry.world} ${entry.soulLayer}
${entry.worldLayer}${entry.worldDescription ? ` · ${entry.worldDescription}` : ""}
${entry.soulLayer}${entry.soulTitle ? ` — ${entry.soulTitle}` : ""}${entry.soulDescription ? `: ${entry.soulDescription}` : ""}
${pathBtn}
`; }).join(""); return card("Qabalistic Worlds & Soul Layers", `
${body}
`); } function normalizeLatinLetter(value) { return String(value || "") .trim() .toUpperCase() .replace(/[^A-Z]/g, ""); } function extractEnglishLetterRefs(value) { if (Array.isArray(value)) { return [...new Set(value.map((entry) => normalizeLatinLetter(entry)).filter(Boolean))]; } return [...new Set( String(value || "") .split(/[\s,;|\/]+/) .map((entry) => normalizeLatinLetter(entry)) .filter(Boolean) )]; } function renderAlphabetEquivalentCard(activeAlphabet, letter) { const hebrewLetters = Array.isArray(state.alphabets?.hebrew) ? state.alphabets.hebrew : []; const greekLetters = Array.isArray(state.alphabets?.greek) ? state.alphabets.greek : []; const englishLetters = Array.isArray(state.alphabets?.english) ? state.alphabets.english : []; const arabicLetters = Array.isArray(state.alphabets?.arabic) ? state.alphabets.arabic : []; const enochianLetters = Array.isArray(state.alphabets?.enochian) ? state.alphabets.enochian : []; const linkedHebrewIds = new Set(); const linkedEnglishLetters = new Set(); const buttons = []; function addHebrewId(value) { const id = normalizeId(value); if (id) { linkedHebrewIds.add(id); } } function addEnglishLetter(value) { const code = normalizeLatinLetter(value); if (!code) { return; } linkedEnglishLetters.add(code); englishLetters .filter((entry) => normalizeLatinLetter(entry?.letter) === code) .forEach((entry) => addHebrewId(entry?.hebrewLetterId)); } if (activeAlphabet === "hebrew") { addHebrewId(letter?.hebrewLetterId); } else if (activeAlphabet === "greek") { addHebrewId(letter?.hebrewLetterId); englishLetters .filter((entry) => normalizeId(entry?.greekEquivalent) === normalizeId(letter?.name)) .forEach((entry) => addEnglishLetter(entry?.letter)); } else if (activeAlphabet === "english") { addEnglishLetter(letter?.letter); addHebrewId(letter?.hebrewLetterId); } else if (activeAlphabet === "arabic") { addHebrewId(letter?.hebrewLetterId); } else if (activeAlphabet === "enochian") { extractEnglishLetterRefs(letter?.englishLetters).forEach((code) => addEnglishLetter(code)); addHebrewId(letter?.hebrewLetterId); } if (!linkedHebrewIds.size && !linkedEnglishLetters.size) { return ""; } const activeHebrewKey = normalizeId(letter?.hebrewLetterId); const activeGreekKey = normalizeId(letter?.name); const activeEnglishKey = normalizeLatinLetter(letter?.letter); const activeArabicKey = normalizeId(letter?.name); const activeEnochianKey = normalizeId(letter?.id || letter?.char || letter?.title); hebrewLetters.forEach((heb) => { const key = normalizeId(heb?.hebrewLetterId); if (!key || !linkedHebrewIds.has(key)) { return; } if (activeAlphabet === "hebrew" && key === activeHebrewKey) { return; } buttons.push(``); }); greekLetters.forEach((grk) => { const key = normalizeId(grk?.name); const viaHebrew = linkedHebrewIds.has(normalizeId(grk?.hebrewLetterId)); const viaEnglish = englishLetters.some((eng) => ( linkedEnglishLetters.has(normalizeLatinLetter(eng?.letter)) && normalizeId(eng?.greekEquivalent) === key )); if (!(viaHebrew || viaEnglish)) { return; } if (activeAlphabet === "greek" && key === activeGreekKey) { return; } buttons.push(``); }); englishLetters.forEach((eng) => { const key = normalizeLatinLetter(eng?.letter); const viaLetter = linkedEnglishLetters.has(key); const viaHebrew = linkedHebrewIds.has(normalizeId(eng?.hebrewLetterId)); if (!(viaLetter || viaHebrew)) { return; } if (activeAlphabet === "english" && key === activeEnglishKey) { return; } buttons.push(``); }); arabicLetters.forEach((arb) => { const key = normalizeId(arb?.name); if (!linkedHebrewIds.has(normalizeId(arb?.hebrewLetterId))) { return; } if (activeAlphabet === "arabic" && key === activeArabicKey) { return; } buttons.push(``); }); enochianLetters.forEach((eno) => { const key = normalizeId(eno?.id || eno?.char || eno?.title); const englishRefs = extractEnglishLetterRefs(eno?.englishLetters); const viaHebrew = linkedHebrewIds.has(normalizeId(eno?.hebrewLetterId)); const viaEnglish = englishRefs.some((code) => linkedEnglishLetters.has(code)); if (!(viaHebrew || viaEnglish)) { return; } if (activeAlphabet === "enochian" && key === activeEnochianKey) { return; } buttons.push(``); }); if (!buttons.length) { return ""; } return card("ALPHABET EQUIVALENT", `
${buttons.join("")}
`); } function renderHebrewDetail(letter) { detailSubEl.textContent = `${letter.name} — ${letter.transliteration}`; detailBodyEl.innerHTML = ""; const sections = []; // Basics sections.push(card("Letter Details", `
Character
${letter.char}
Name
${letter.name}
Transliteration
${letter.transliteration}
Meaning
${letter.meaning}
Gematria Value
${letter.numerology}
Letter Type
${letter.letterType}
Position
#${letter.index} of 22
`)); const positionRootCard = renderPositionDigitalRootCard(letter, "hebrew"); if (positionRootCard) { sections.push(positionRootCard); } if (letter.letterType === "double") { const dualityCard = renderHebrewDualityCard(letter); if (dualityCard) { sections.push(dualityCard); } } const fourWorldsCard = renderHebrewFourWorldsCard(letter); if (fourWorldsCard) { sections.push(fourWorldsCard); } // Astrology if (letter.astrology) { sections.push(renderAstrologyCard(letter.astrology)); } // Kabbalah Path + Tarot if (letter.kabbalahPathNumber) { const tarotPart = letter.tarot ? `
Tarot Card
${letter.tarot.card} (Trump ${letter.tarot.trumpNumber})
` : ""; const kabBtn = navBtn("View Kabbalah Path", "tarot:view-kab-path", { "path-number": letter.kabbalahPathNumber }); const tarotBtn = letter.tarot ? navBtn("View Tarot Card", "kab:view-trump", { "trump-number": letter.tarot.trumpNumber }) : ""; const cubePlacement = getCubePlacementForHebrewLetter(letter.hebrewLetterId, letter.kabbalahPathNumber); const cubeBtn = cubePlacementBtn(cubePlacement, { "hebrew-letter-id": letter.hebrewLetterId, "path-no": letter.kabbalahPathNumber }); sections.push(card("Kabbalah & Tarot", `
Path Number
${letter.kabbalahPathNumber}
${tarotPart}
${kabBtn}${tarotBtn}${cubeBtn}
`)); } const monthRefs = monthRefsForLetter(letter); const monthCard = calendarMonthsCard(monthRefs, `Calendar correspondences linked to ${letter.name}.`); if (monthCard) { sections.push(monthCard); } const equivalentsCard = renderAlphabetEquivalentCard("hebrew", letter); if (equivalentsCard) { sections.push(equivalentsCard); } detailBodyEl.innerHTML = sections.join(""); attachDetailListeners(); } function renderGreekDetail(letter) { const archaicBadge = letter.archaic ? ' archaic' : ""; detailSubEl.textContent = `${letter.displayName}${letter.archaic ? " (archaic)" : ""} — ${letter.transliteration}`; detailBodyEl.innerHTML = ""; const sections = []; const charRow = letter.charFinal ? `
Form (final)
${letter.charFinal}
` : ""; sections.push(card("Letter Details", `
Uppercase
${letter.char}
Lowercase
${letter.charLower || "—"}
${charRow}
Name
${letter.displayName}${archaicBadge}
Transliteration
${letter.transliteration}
IPA
${letter.ipa || "—"}
Isopsephy Value
${letter.numerology}
Meaning / Origin
${letter.meaning || "—"}
`)); const positionRootCard = renderPositionDigitalRootCard(letter, "greek"); if (positionRootCard) { sections.push(positionRootCard); } const equivalentsCard = renderAlphabetEquivalentCard("greek", letter); if (equivalentsCard) { sections.push(equivalentsCard); } const monthRefs = monthRefsForLetter(letter); const monthCard = calendarMonthsCard(monthRefs, `Calendar correspondences inherited via ${letter.displayName}'s Hebrew origin.`); if (monthCard) { sections.push(monthCard); } detailBodyEl.innerHTML = sections.join(""); attachDetailListeners(); } function renderEnglishDetail(letter) { detailSubEl.textContent = `Letter ${letter.letter} · position #${letter.index}`; detailBodyEl.innerHTML = ""; const sections = []; sections.push(card("Letter Details", `
Letter
${letter.letter}
Position
#${letter.index} of 26
IPA
${letter.ipa || "—"}
Pythagorean Value
${letter.pythagorean}
`)); const positionRootCard = renderPositionDigitalRootCard(letter, "english"); if (positionRootCard) { sections.push(positionRootCard); } const equivalentsCard = renderAlphabetEquivalentCard("english", letter); if (equivalentsCard) { sections.push(equivalentsCard); } const monthRefs = monthRefsForLetter(letter); const monthCard = calendarMonthsCard(monthRefs, `Calendar correspondences linked through this letter's Hebrew correspondence.`); if (monthCard) { sections.push(monthCard); } detailBodyEl.innerHTML = sections.join(""); attachDetailListeners(); } function renderArabicDetail(letter) { detailSubEl.textContent = `${arabicDisplayName(letter)} — ${letter.transliteration}`; detailBodyEl.innerHTML = ""; const sections = []; // Letter forms row const f = letter.forms || {}; const formParts = [ f.isolated ? `${f.isolated}
isolated
` : "", f.final ? `${f.final}
final
` : "", f.medial ? `${f.medial}
medial
` : "", f.initial ? `${f.initial}
initial
` : "" ].filter(Boolean); sections.push(card("Letter Details", `
Arabic Name
${letter.nameArabic}
Transliteration
${letter.transliteration}
IPA
${letter.ipa || "—"}
Abjad Value
${letter.abjad}
Meaning
${letter.meaning || "—"}
Category
${letter.category}
Position
#${letter.index} of 28 (Abjad order)
`)); const positionRootCard = renderPositionDigitalRootCard(letter, "arabic", "Abjad order"); if (positionRootCard) { sections.push(positionRootCard); } if (formParts.length) { sections.push(card("Letter Forms", `
${formParts.join("")}
`)); } const equivalentsCard = renderAlphabetEquivalentCard("arabic", letter); if (equivalentsCard) { sections.push(equivalentsCard); } detailBodyEl.innerHTML = sections.join(""); attachDetailListeners(); } function renderEnochianDetail(letter) { const englishRefs = extractEnglishLetterRefs(letter?.englishLetters); detailSubEl.textContent = `${letter.title} — ${letter.transliteration}`; detailBodyEl.innerHTML = ""; const sections = []; sections.push(card("Letter Details", `
Character
${enochianGlyphImageHtml(letter, "alpha-enochian-glyph-img alpha-enochian-glyph-img--detail-row")}
Name
${letter.title}
English Letters
${englishRefs.join(" / ") || "—"}
Transliteration
${letter.transliteration || "—"}
Element / Planet
${letter.elementOrPlanet || "—"}
Tarot
${letter.tarot || "—"}
Numerology
${letter.numerology || "—"}
Glyph Source
Local cache: asset/img/enochian (sourced from dCode set)
Position
#${letter.index} of 21
`)); const positionRootCard = renderPositionDigitalRootCard(letter, "enochian"); if (positionRootCard) { sections.push(positionRootCard); } const equivalentsCard = renderAlphabetEquivalentCard("enochian", letter); if (equivalentsCard) { sections.push(equivalentsCard); } const monthRefs = monthRefsForLetter(letter); const monthCard = calendarMonthsCard(monthRefs, `Calendar correspondences linked through this letter's Hebrew correspondence.`); if (monthCard) { sections.push(monthCard); } detailBodyEl.innerHTML = sections.join(""); attachDetailListeners(); } // ── Event delegation on detail body ────────────────────────────────── function attachDetailListeners() { if (!detailBodyEl) return; // Nav buttons — generic: forward all data-* (except data-event) as the event detail detailBodyEl.querySelectorAll(".alpha-nav-btn[data-event]").forEach((btn) => { btn.addEventListener("click", () => { const evtName = btn.dataset.event; const detail = {}; Object.entries(btn.dataset).forEach(([key, val]) => { if (key === "event") return; // Convert kebab data keys to camelCase (e.g. planet-id → planetId) const camel = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); detail[camel] = isNaN(Number(val)) || val === "" ? val : Number(val); }); document.dispatchEvent(new CustomEvent(evtName, { detail })); }); }); // Sister letter cross-navigation within this section detailBodyEl.querySelectorAll(".alpha-sister-btn[data-alpha]").forEach((btn) => { btn.addEventListener("click", () => { const alpha = btn.dataset.alpha; const key = btn.dataset.key; switchAlphabet(alpha, key); }); }); } // ── Selection ───────────────────────────────────────────────────────── function selectLetter(key) { state.selectedKey = key; renderList(); const letters = getFilteredLetters(); const letter = letters.find((l) => letterKey(l) === key) || getLetters().find((l) => letterKey(l) === key); if (letter) renderDetail(letter); } // ── Alphabet switching ──────────────────────────────────────────────── function switchAlphabet(alpha, selectKey) { state.activeAlphabet = alpha; state.selectedKey = selectKey || null; updateTabs(); syncFilterControls(); renderList(); if (selectKey) { const letters = getFilteredLetters(); const letter = letters.find((l) => letterKey(l) === selectKey) || getLetters().find((l) => letterKey(l) === selectKey); if (letter) renderDetail(letter); } else { resetDetail(); } } function updateTabs() { [tabAll, tabHebrew, tabGreek, tabEnglish, tabArabic, tabEnochian].forEach((btn) => { if (!btn) return; btn.classList.toggle("alpha-tab-active", btn.dataset.alpha === state.activeAlphabet); }); } function resetDetail() { if (detailNameEl) { detailNameEl.textContent = "--"; detailNameEl.classList.remove("alpha-detail-glyph"); } if (detailSubEl) detailSubEl.textContent = "Select a letter to explore"; if (detailBodyEl) detailBodyEl.innerHTML = ""; } // ── Public init ─────────────────────────────────────────────────────── function ensureAlphabetSection(magickDataset, referenceData = null) { const grouped = magickDataset?.grouped || {}; const alphabetData = (grouped["alphabets"] && grouped["alphabets"]["hebrew"]) ? grouped["alphabets"] : null; if (alphabetData) { state.alphabets = alphabetData; if (state.gematria.db?.baseAlphabet) { refreshGematriaScriptMap(state.gematria.db.baseAlphabet); } } state.fourWorldLayers = buildFourWorldLayersFromDataset(magickDataset); state.cubeRefs = buildCubeReferences(magickDataset); if (referenceData && state.alphabets) { state.monthRefsByHebrewId = buildMonthReferencesByHebrew(referenceData, state.alphabets); } if (state.initialized) { ensureGematriaCalculator(); syncFilterControls(); renderList(); const letters = getFilteredLetters(); const selected = letters.find((entry) => letterKey(entry) === state.selectedKey) || letters[0]; if (selected) { renderDetail(selected); } else { resetDetail(); if (detailSubEl) { detailSubEl.textContent = "No letters match the current filter."; } } return; } state.initialized = true; // alphabets.json is a top-level file → grouped["alphabets"] = the data object getElements(); ensureGematriaCalculator(); bindFilterControls(); syncFilterControls(); if (!state.alphabets) { if (detailSubEl) detailSubEl.textContent = "Alphabet data not loaded."; return; } // Attach tab listeners [tabAll, tabHebrew, tabGreek, tabEnglish, tabArabic, tabEnochian].forEach((btn) => { if (!btn) return; btn.addEventListener("click", () => { switchAlphabet(btn.dataset.alpha, null); }); }); switchAlphabet("all", null); } // ── Incoming navigation ─────────────────────────────────────────────── function selectLetterByHebrewId(hebrewLetterId) { switchAlphabet("hebrew", hebrewLetterId); } function selectGreekLetterByName(name) { switchAlphabet("greek", name); } function selectEnglishLetter(letter) { switchAlphabet("english", letter); } function selectArabicLetter(name) { switchAlphabet("arabic", name); } function selectEnochianLetter(id) { switchAlphabet("enochian", id); } window.AlphabetSectionUi = { ensureAlphabetSection, selectLetterByHebrewId, selectGreekLetterByName, selectEnglishLetter, selectArabicLetter, selectEnochianLetter }; })();