(function () { "use strict"; // ─── SVG tree layout constants ────────────────────────────────────────────── const NS = "http://www.w3.org/2000/svg"; const R = 11; // sephira circle radius // Standard Hermetic GD Tree of Life positions in a 240×470 viewBox const NODE_POS = { 1: [120, 30], // Kether — crown of middle pillar 2: [200, 88], // Chokmah — right pillar 3: [40, 88], // Binah — left pillar 4: [200, 213], // Chesed — right pillar 5: [40, 213], // Geburah — left pillar 6: [120, 273], // Tiphareth — middle pillar 7: [200, 343], // Netzach — right pillar 8: [40, 343], // Hod — left pillar 9: [120, 398], // Yesod — middle pillar 10: [120, 448], // Malkuth — bottom of middle pillar }; // King-scale fill colours const SEPH_FILL = { 1: "#e8e8e8", // Kether — white brilliance 2: "#87ceeb", // Chokmah — soft sky blue 3: "#7d2b5a", // Binah — dark crimson 4: "#1a56e0", // Chesed — deep blue 5: "#d44014", // Geburah — scarlet 6: "#d4a017", // Tiphareth — gold 7: "#22aa60", // Netzach — emerald 8: "#cc5500", // Hod — orange 9: "#6030c0", // Yesod — violet 10: "#c8b000", // Malkuth — citrine / amber }; // Nodes with light-ish fills get dark number text; others get white const DARK_TEXT = new Set([1, 2, 6, 10]); // Da'at – phantom sephira drawn as a dashed circle, not clickable const DAAT = [120, 148]; const PATH_MARKER_SCALE = 1.33; const PATH_LABEL_RADIUS = 9 * PATH_MARKER_SCALE; const PATH_LABEL_FONT_SIZE = 8.8 * PATH_MARKER_SCALE; const PATH_TAROT_WIDTH = 16 * PATH_MARKER_SCALE; const PATH_TAROT_HEIGHT = 24 * PATH_MARKER_SCALE; const PATH_LABEL_OFFSET_WITH_TAROT = 11 * PATH_MARKER_SCALE; const PATH_TAROT_OFFSET_WITH_LABEL = 1 * PATH_MARKER_SCALE; const PATH_TAROT_OFFSET_NO_LABEL = 12 * PATH_MARKER_SCALE; // ─── state ────────────────────────────────────────────────────────────────── const state = { initialized: false, tree: null, godsData: {}, hebrewLetterIdByToken: {}, fourWorldLayers: [], showPathLetters: true, showPathNumbers: true, showPathTarotCards: false, selectedSephiraNumber: null, selectedPathNumber: null }; const kabbalahDetailUi = window.KabbalahDetailUi || {}; const kabbalahViewsUi = window.KabbalahViewsUi || {}; if ( typeof kabbalahViewsUi.renderTree !== "function" || typeof kabbalahViewsUi.renderRoseCross !== "function" ) { throw new Error("KabbalahViewsUi module must load before ui-kabbalah.js"); } const PLANET_NAME_TO_ID = { saturn: "saturn", jupiter: "jupiter", mars: "mars", sol: "sol", sun: "sol", venus: "venus", mercury: "mercury", luna: "luna", moon: "luna" }; const ZODIAC_NAME_TO_ID = { aries: "aries", taurus: "taurus", gemini: "gemini", cancer: "cancer", leo: "leo", virgo: "virgo", libra: "libra", scorpio: "scorpio", sagittarius: "sagittarius", capricorn: "capricorn", aquarius: "aquarius", pisces: "pisces" }; const HEBREW_LETTER_ALIASES = { aleph: "alef", alef: "alef", beth: "bet", bet: "bet", gimel: "gimel", daleth: "dalet", dalet: "dalet", he: "he", vav: "vav", zayin: "zayin", cheth: "het", chet: "het", het: "het", teth: "tet", tet: "tet", yod: "yod", kaph: "kaf", kaf: "kaf", lamed: "lamed", mem: "mem", nun: "nun", samekh: "samekh", ayin: "ayin", pe: "pe", tzaddi: "tsadi", tzadi: "tsadi", tsadi: "tsadi", qoph: "qof", qof: "qof", resh: "resh", shin: "shin", tav: "tav" }; const DEFAULT_FOUR_QABALISTIC_WORLD_LAYERS = [ { slot: "Yod", letterChar: "י", hebrewToken: "yod", world: "Atziluth", worldLayer: "Archetypal World (God’s Will)", worldDescription: "World of gods or specific facets or divine qualities.", soulLayer: "Chiah", soulTitle: "Life Force", soulDescription: "The Chiah is the Life Force itself and our true identity as reflection of Supreme Consciousness." }, { slot: "Heh", letterChar: "ה", hebrewToken: "he", world: "Briah", worldLayer: "Creative World (God’s Love)", worldDescription: "World of archangels, executors of divine qualities.", soulLayer: "Neshamah", soulTitle: "Soul-Intuition", soulDescription: "The Neshamah is the part of our soul that transcends the thinking process." }, { slot: "Vav", letterChar: "ו", hebrewToken: "vav", world: "Yetzirah", worldLayer: "Formative World (God’s Mind)", worldDescription: "World of angels who work under archangelic direction.", soulLayer: "Ruach", soulTitle: "Intellect", soulDescription: "The Ruach is the thinking mind that often dominates attention and identity." }, { slot: "Heh (final)", letterChar: "ה", hebrewToken: "he", world: "Assiah", worldLayer: "Material World (God’s Creation)", worldDescription: "World of spirits that infuse matter and energy through specialized duties.", soulLayer: "Nephesh", soulTitle: "Animal Soul", soulDescription: "The Nephesh is instinctive consciousness expressed through appetite, emotion, sex drive, and survival." } ]; function titleCase(value) { return String(value || "") .split(/[\s-_]+/) .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } 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; if (!worlds || typeof worlds !== "object") { return [...DEFAULT_FOUR_QABALISTIC_WORLD_LAYERS]; } const worldOrder = ["atzilut", "briah", "yetzirah", "assiah"]; const soulAliases = { chiah: "chaya", chaya: "chaya", neshamah: "neshama", neshama: "neshama", ruach: "ruach", nephesh: "nephesh" }; return worldOrder.map((worldId, index) => { const fallback = DEFAULT_FOUR_QABALISTIC_WORLD_LAYERS[index] || {}; const worldEntry = worlds?.[worldId] || null; if (!worldEntry || typeof worldEntry !== "object") { return fallback; } const tetragrammaton = worldEntry?.tetragrammaton && typeof worldEntry.tetragrammaton === "object" ? worldEntry.tetragrammaton : {}; const rawSoulId = normalizeSoulId(worldEntry?.soulId); const soulId = soulAliases[rawSoulId] || rawSoulId; const soulEntry = souls?.[soulId] && typeof souls[soulId] === "object" ? souls[soulId] : null; const soulLayer = soulEntry?.name?.roman || fallback.soulLayer || titleCase(rawSoulId || soulId); const soulTitle = soulEntry?.title?.en || fallback.soulTitle || titleCase(soulEntry?.name?.en || ""); const soulDescription = soulEntry?.desc?.en || fallback.soulDescription || ""; return { slot: tetragrammaton?.isFinal ? `${String(tetragrammaton?.slot || fallback.slot || "Heh")} (final)` : String(tetragrammaton?.slot || fallback.slot || ""), letterChar: String(tetragrammaton?.letterChar || fallback.letterChar || ""), hebrewToken: String(tetragrammaton?.hebrewLetterId || fallback.hebrewToken || "").toLowerCase(), world: String(worldEntry?.name?.roman || fallback.world || titleCase(worldEntry?.id || worldId)), worldLayer: String(worldEntry?.worldLayer?.en || fallback.worldLayer || worldEntry?.desc?.en || ""), worldDescription: String(worldEntry?.worldDescription?.en || fallback.worldDescription || ""), soulLayer: String(soulLayer || ""), soulTitle: String(soulTitle || ""), soulDescription: String(soulDescription || "") }; }).filter(Boolean); } // ─── element references ───────────────────────────────────────────────────── function getElements() { return { treeContainerEl: document.getElementById("kab-tree-container"), detailNameEl: document.getElementById("kab-detail-name"), detailSubEl: document.getElementById("kab-detail-sub"), detailBodyEl: document.getElementById("kab-detail-body"), pathLetterToggleEl: document.getElementById("kab-path-letter-toggle"), pathNumberToggleEl: document.getElementById("kab-path-number-toggle"), pathTarotToggleEl: document.getElementById("kab-path-tarot-toggle"), roseCrossContainerEl: document.getElementById("kab-rose-cross-container"), roseDetailNameEl: document.getElementById("kab-rose-detail-name"), roseDetailSubEl: document.getElementById("kab-rose-detail-sub"), roseDetailBodyEl: document.getElementById("kab-rose-detail-body"), }; } function getRoseDetailElements(elements) { if (!elements) { return null; } return { detailNameEl: elements.roseDetailNameEl, detailSubEl: elements.roseDetailSubEl, detailBodyEl: elements.roseDetailBodyEl }; } function normalizeText(value) { return String(value || "").trim().toLowerCase(); } function normalizeLetterToken(value) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z]/g, ""); } function buildHebrewLetterLookup(magickDataset) { const letters = magickDataset?.grouped?.hebrewLetters; const lookup = {}; if (!letters || typeof letters !== "object") { return lookup; } Object.entries(letters).forEach(([letterId, entry]) => { const entryId = String(entry?.id || letterId || ""); const idToken = normalizeLetterToken(letterId); const canonicalIdToken = HEBREW_LETTER_ALIASES[idToken] || idToken; if (canonicalIdToken && !lookup[canonicalIdToken]) { lookup[canonicalIdToken] = entryId; } const nameToken = normalizeLetterToken(entry?.letter?.name); const canonicalNameToken = HEBREW_LETTER_ALIASES[nameToken] || nameToken; if (canonicalNameToken && !lookup[canonicalNameToken]) { lookup[canonicalNameToken] = entryId; } }); return lookup; } function resolveHebrewLetterId(value) { const token = normalizeLetterToken(value); if (!token) return null; const canonical = HEBREW_LETTER_ALIASES[token] || token; return state.hebrewLetterIdByToken[canonical] || state.hebrewLetterIdByToken[token] || null; } function resolvePlanetId(value) { const text = normalizeText(value); if (!text) return null; for (const [key, planetId] of Object.entries(PLANET_NAME_TO_ID)) { if (text === key || text.includes(key)) { return planetId; } } return null; } function resolveZodiacId(value) { const text = normalizeText(value); if (!text) return null; for (const [name, zodiacId] of Object.entries(ZODIAC_NAME_TO_ID)) { if (text === name || text.includes(name)) { return zodiacId; } } return null; } function findPathByHebrewToken(tree, hebrewToken) { const canonicalToken = HEBREW_LETTER_ALIASES[normalizeLetterToken(hebrewToken)] || normalizeLetterToken(hebrewToken); if (!canonicalToken) { return null; } const paths = Array.isArray(tree?.paths) ? tree.paths : []; return paths.find((path) => { const letterToken = normalizeLetterToken(path?.hebrewLetter?.transliteration || path?.hebrewLetter?.char); const canonicalLetterToken = HEBREW_LETTER_ALIASES[letterToken] || letterToken; return canonicalLetterToken === canonicalToken; }) || null; } function getDetailRenderContext(tree, elements, extra = {}) { return { tree, elements, godsData: state.godsData, fourWorldLayers: state.fourWorldLayers, resolvePlanetId, resolveZodiacId, resolveHebrewLetterId, findPathByHebrewToken, ...extra }; } function clearHighlights() { document.querySelectorAll(".kab-node, .kab-node-glow") .forEach(el => el.classList.remove("kab-node-active")); document.querySelectorAll(".kab-path-hit, .kab-path-line, .kab-path-lbl, .kab-path-tarot, .kab-rose-petal") .forEach(el => el.classList.remove("kab-path-active")); } function renderSephiraDetail(seph, tree, elements) { state.selectedSephiraNumber = Number(seph?.number); state.selectedPathNumber = null; clearHighlights(); document.querySelectorAll(`.kab-node[data-sephira="${seph.number}"], .kab-node-glow[data-sephira="${seph.number}"]`) .forEach(el => el.classList.add("kab-node-active")); if (typeof kabbalahDetailUi.renderSephiraDetail === "function") { kabbalahDetailUi.renderSephiraDetail(getDetailRenderContext(tree, elements, { seph, onPathSelect: (path) => renderPathDetail(path, tree, elements) })); } } function renderPathDetail(path, tree, elements) { state.selectedPathNumber = Number(path?.pathNumber); state.selectedSephiraNumber = null; clearHighlights(); document.querySelectorAll(`[data-path="${path.pathNumber}"]`) .forEach(el => el.classList.add("kab-path-active")); if (typeof kabbalahDetailUi.renderPathDetail === "function") { kabbalahDetailUi.renderPathDetail(getDetailRenderContext(tree, elements, { path, activeHebrewToken: normalizeLetterToken(path?.hebrewLetter?.transliteration || path?.hebrewLetter?.char || "") })); } } function renderRoseLandingIntro(roseElements) { if (typeof kabbalahDetailUi.renderRoseLandingIntro === "function") { kabbalahDetailUi.renderRoseLandingIntro(roseElements); } } function getViewRenderContext(elements) { return { state, tree: state.tree, elements, getRoseDetailElements, renderSephiraDetail, renderPathDetail, NS, R, NODE_POS, SEPH_FILL, DARK_TEXT, DAAT, PATH_MARKER_SCALE, PATH_LABEL_RADIUS, PATH_LABEL_FONT_SIZE, PATH_TAROT_WIDTH, PATH_TAROT_HEIGHT, PATH_LABEL_OFFSET_WITH_TAROT, PATH_TAROT_OFFSET_WITH_LABEL, PATH_TAROT_OFFSET_NO_LABEL }; } function renderRoseCurrentSelection(elements) { if (!state.tree) { return; } const roseElements = getRoseDetailElements(elements); if (!roseElements?.detailBodyEl) { return; } if (Number.isFinite(Number(state.selectedPathNumber))) { const selectedPath = state.tree.paths.find((entry) => entry.pathNumber === Number(state.selectedPathNumber)); if (selectedPath) { renderPathDetail(selectedPath, state.tree, roseElements); return; } } renderRoseLandingIntro(roseElements); } function renderRoseCross(elements) { kabbalahViewsUi.renderRoseCross(getViewRenderContext(elements)); } function renderTree(elements) { kabbalahViewsUi.renderTree(getViewRenderContext(elements)); } function renderCurrentSelection(elements) { if (!state.tree) { return; } if (Number.isFinite(Number(state.selectedPathNumber))) { const selectedPath = state.tree.paths.find((entry) => entry.pathNumber === Number(state.selectedPathNumber)); if (selectedPath) { renderPathDetail(selectedPath, state.tree, elements); return; } } if (Number.isFinite(Number(state.selectedSephiraNumber))) { const selectedSephira = state.tree.sephiroth.find((entry) => entry.number === Number(state.selectedSephiraNumber)); if (selectedSephira) { renderSephiraDetail(selectedSephira, state.tree, elements); return; } } renderSephiraDetail(state.tree.sephiroth[0], state.tree, elements); } // ─── initialise section ────────────────────────────────────────────────────── function init(magickDataset, elements) { const tree = magickDataset?.grouped?.kabbalah?.["kabbalah-tree"]; if (!tree) { if (elements.detailNameEl) { elements.detailNameEl.textContent = "Kabbalah data unavailable"; elements.detailSubEl.textContent = "Could not load kabbalah-tree.json"; } return; } state.tree = tree; state.godsData = magickDataset?.grouped?.["gods"]?.byPath || {}; state.hebrewLetterIdByToken = buildHebrewLetterLookup(magickDataset); state.fourWorldLayers = buildFourWorldLayersFromDataset(magickDataset); const bindPathDisplayToggle = (toggleEl, stateKey) => { if (!toggleEl) { return; } toggleEl.checked = Boolean(state[stateKey]); if (toggleEl.dataset.bound) { return; } toggleEl.addEventListener("change", () => { state[stateKey] = Boolean(toggleEl.checked); renderTree(elements); renderCurrentSelection(elements); }); toggleEl.dataset.bound = "true"; }; bindPathDisplayToggle(elements.pathLetterToggleEl, "showPathLetters"); bindPathDisplayToggle(elements.pathNumberToggleEl, "showPathNumbers"); bindPathDisplayToggle(elements.pathTarotToggleEl, "showPathTarotCards"); renderTree(elements); renderCurrentSelection(elements); renderRoseCross(elements); renderRoseCurrentSelection(elements); } function selectPathByNumber(pathNumber) { if (!state.initialized || !state.tree) return; const el = getElements(); const path = state.tree.paths.find(p => p.pathNumber === pathNumber); if (path) renderPathDetail(path, state.tree, el); } function selectSephiraByNumber(n) { if (!state.initialized || !state.tree) return; const el = getElements(); const seph = state.tree.sephiroth.find(s => s.number === n); if (seph) renderSephiraDetail(seph, state.tree, el); } // select sephirah (1-10) or path (11+) by a single number function selectNode(n) { if (n >= 1 && n <= 10) selectSephiraByNumber(n); else selectPathByNumber(n); } // ─── public API ──────────────────────────────────────────────────────── function ensureKabbalahSection(magickDataset) { if (state.initialized) return; state.initialized = true; const elements = getElements(); init(magickDataset, elements); } window.KabbalahSectionUi = { ensureKabbalahSection, selectPathByNumber, selectSephiraByNumber, selectNode }; })();