/* 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 `
`;
}
// ── 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 `
`;
}
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
};
})();