(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 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 resolvePathTarotImage(path) { const cardName = String(path?.tarot?.card || "").trim(); if (!cardName || typeof window.TarotCardImages?.resolveTarotCardImage !== "function") { return null; } return window.TarotCardImages.resolveTarotCardImage(cardName); } function getSvgImageHref(imageEl) { if (!(imageEl instanceof SVGElement)) { return ""; } return String( imageEl.getAttribute("href") || imageEl.getAttributeNS("http://www.w3.org/1999/xlink", "href") || "" ).trim(); } function openTarotLightboxForPath(path, fallbackSrc = "") { const openLightbox = window.TarotUiLightbox?.open; if (typeof openLightbox !== "function") { return false; } const cardName = String(path?.tarot?.card || "").trim(); const src = String(fallbackSrc || resolvePathTarotImage(path) || "").trim(); if (!src) { return false; } const fallbackLabel = Number.isFinite(Number(path?.pathNumber)) ? `Path ${path.pathNumber} tarot card` : "Path tarot card"; openLightbox(src, cardName || fallbackLabel); return true; } function getPathLabel(path) { const glyph = String(path?.hebrewLetter?.char || "").trim(); const pathNumber = Number(path?.pathNumber); const parts = []; if (state.showPathLetters && glyph) { parts.push(glyph); } if (state.showPathNumbers && Number.isFinite(pathNumber)) { parts.push(String(pathNumber)); } return parts.join(" "); } // ─── SVG element factory ──────────────────────────────────────────────────── function svgEl(tag, attrs, text) { const el = document.createElementNS(NS, tag); for (const [k, v] of Object.entries(attrs || {})) { el.setAttribute(k, String(v)); } if (text != null) el.textContent = text; return el; } // Rosicrucian cross SVG construction lives in app/ui-rosicrucian-cross.js. // ─── build the full SVG tree ───────────────────────────────────────────────── function buildTreeSVG(tree) { const svg = svgEl("svg", { viewBox: "0 0 240 470", width: "100%", role: "img", "aria-label": "Kabbalah Tree of Life diagram", class: "kab-svg", }); // Subtle pillar background tracks svg.appendChild(svgEl("rect", { x: 113, y: 30, width: 14, height: 420, rx: 7, fill: "#ffffff07", "pointer-events": "none", })); svg.appendChild(svgEl("rect", { x: 33, y: 88, width: 14, height: 255, rx: 7, fill: "#ff220010", "pointer-events": "none", })); svg.appendChild(svgEl("rect", { x: 193, y: 88, width: 14, height: 255, rx: 7, fill: "#2244ff10", "pointer-events": "none", })); // Pillar labels [ { x: 198, y: 73, text: "Mercy", anchor: "middle" }, { x: 120, y: 17, text: "Balance", anchor: "middle" }, { x: 42, y: 73, text: "Severity", anchor: "middle" }, ].forEach(({ x, y, text, anchor }) => { svg.appendChild(svgEl("text", { x, y, "text-anchor": anchor, "dominant-baseline": "auto", fill: "#42425a", "font-size": "6", "pointer-events": "none", }, text)); }); // ── path lines (drawn before sephiroth so nodes sit on top) ────────────── tree.paths.forEach(path => { const [x1, y1] = NODE_POS[path.connects.from]; const [x2, y2] = NODE_POS[path.connects.to]; const mx = (x1 + x2) / 2; const my = (y1 + y2) / 2; const tarotImage = state.showPathTarotCards ? resolvePathTarotImage(path) : null; const hasTarotImage = Boolean(tarotImage); const pathLabel = getPathLabel(path); const hasLabel = Boolean(pathLabel); const labelY = hasTarotImage && hasLabel ? my - PATH_LABEL_OFFSET_WITH_TAROT : my; // Visual line (thin) svg.appendChild(svgEl("line", { x1, y1, x2, y2, class: "kab-path-line", "data-path": path.pathNumber, stroke: "#3c3c5c", "stroke-width": "1.5", "pointer-events": "none", })); // Invisible wide hit area for easy clicking svg.appendChild(svgEl("line", { x1, y1, x2, y2, class: "kab-path-hit", "data-path": path.pathNumber, stroke: "transparent", "stroke-width": String(12 * PATH_MARKER_SCALE), role: "button", tabindex: "0", "aria-label": `Path ${path.pathNumber}: ${path.hebrewLetter?.transliteration || ""} — ${path.tarot?.card || ""}`, style: "cursor:pointer", })); if (hasLabel) { // Background disc for legibility behind path label svg.appendChild(svgEl("circle", { cx: mx, cy: labelY, r: PATH_LABEL_RADIUS.toFixed(2), fill: "#0d0d1c", opacity: "0.82", "pointer-events": "none", })); // Path label at path midpoint svg.appendChild(svgEl("text", { x: mx, y: labelY + 1, "text-anchor": "middle", "dominant-baseline": "middle", class: "kab-path-lbl", "data-path": path.pathNumber, fill: "#a8a8e0", "font-size": PATH_LABEL_FONT_SIZE.toFixed(2), "pointer-events": "none", }, pathLabel)); } if (hasTarotImage) { const tarotY = hasLabel ? my + PATH_TAROT_OFFSET_WITH_LABEL : my - PATH_TAROT_OFFSET_NO_LABEL; svg.appendChild(svgEl("image", { href: tarotImage, x: (mx - (PATH_TAROT_WIDTH / 2)).toFixed(2), y: tarotY.toFixed(2), width: PATH_TAROT_WIDTH.toFixed(2), height: PATH_TAROT_HEIGHT.toFixed(2), preserveAspectRatio: "xMidYMid meet", class: "kab-path-tarot", "data-path": path.pathNumber, role: "button", tabindex: "0", "aria-label": `Path ${path.pathNumber} Tarot card ${path.tarot?.card || ""}`, style: "cursor:pointer" })); } }); // ── Da'at — phantom sephira (dashed, informational only) ──────────────── svg.appendChild(svgEl("circle", { cx: DAAT[0], cy: DAAT[1], r: "9", fill: "none", stroke: "#3c3c5c", "stroke-dasharray": "3 2", "stroke-width": "1", "pointer-events": "none", })); svg.appendChild(svgEl("text", { x: DAAT[0] + 13, y: DAAT[1] + 1, "text-anchor": "start", "dominant-baseline": "middle", fill: "#3c3c5c", "font-size": "6.5", "pointer-events": "none", }, "Da'at")); // ── sephiroth circles (drawn last, on top of paths) ────────────────────── tree.sephiroth.forEach(seph => { const [cx, cy] = NODE_POS[seph.number]; const fill = SEPH_FILL[seph.number] || "#555"; const isLeft = cx < 80; const isMid = cx === 120; // Glow halo (subtle, pointer-events:none) svg.appendChild(svgEl("circle", { cx, cy, r: "16", fill, opacity: "0.12", class: "kab-node-glow", "data-sephira": seph.number, "pointer-events": "none", })); // Main clickable circle svg.appendChild(svgEl("circle", { cx, cy, r: R, fill, stroke: "#00000040", "stroke-width": "1", class: "kab-node", "data-sephira": seph.number, role: "button", tabindex: "0", "aria-label": `Sephira ${seph.number}: ${seph.name}`, style: "cursor:pointer", })); // Sephira number inside the circle svg.appendChild(svgEl("text", { x: cx, y: cy + 0.5, "text-anchor": "middle", "dominant-baseline": "middle", fill: DARK_TEXT.has(seph.number) ? "#111" : "#fff", "font-size": "8", "font-weight": "bold", "pointer-events": "none", }, String(seph.number))); // Name label beside the circle const lx = isLeft ? cx - R - 4 : cx + R + 4; svg.appendChild(svgEl("text", { x: isMid ? cx : lx, y: isMid ? cy + R + 8 : cy, "text-anchor": isMid ? "middle" : (isLeft ? "end" : "start"), "dominant-baseline": isMid ? "auto" : "middle", fill: "#c0c0d4", "font-size": "7.5", "pointer-events": "none", class: "kab-node-lbl", }, seph.name)); }); return svg; } 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 bindTreeInteractions(svg, tree, elements) { // Delegate clicks via element's own data attributes svg.addEventListener("click", e => { const clickTarget = e.target instanceof Element ? e.target : null; const sephNum = clickTarget?.dataset?.sephira; const pathNum = clickTarget?.dataset?.path; if (pathNum != null && clickTarget?.classList?.contains("kab-path-tarot")) { const p = tree.paths.find(x => x.pathNumber === Number(pathNum)); if (p) { openTarotLightboxForPath(p, getSvgImageHref(clickTarget)); renderPathDetail(p, tree, elements); } return; } if (sephNum != null) { const s = tree.sephiroth.find(x => x.number === Number(sephNum)); if (s) renderSephiraDetail(s, tree, elements); } else if (pathNum != null) { const p = tree.paths.find(x => x.pathNumber === Number(pathNum)); if (p) renderPathDetail(p, tree, elements); } }); // Keyboard access for path hit-areas and tarot images svg.querySelectorAll(".kab-path-hit, .kab-path-tarot").forEach(el => { el.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); const p = tree.paths.find(x => x.pathNumber === Number(el.dataset.path)); if (p) { if (el.classList.contains("kab-path-tarot")) { openTarotLightboxForPath(p, getSvgImageHref(el)); } renderPathDetail(p, tree, elements); } } }); }); // Keyboard access for sephira circles svg.querySelectorAll(".kab-node").forEach(el => { el.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); const s = tree.sephiroth.find(x => x.number === Number(el.dataset.sephira)); if (s) renderSephiraDetail(s, tree, elements); } }); }); } function bindRoseCrossInteractions(svg, tree, roseElements) { if (!svg || !roseElements?.detailBodyEl) { return; } const openPathFromTarget = (targetEl) => { if (!(targetEl instanceof Element)) { return; } const petal = targetEl.closest(".kab-rose-petal[data-path]"); if (!(petal instanceof SVGElement)) { return; } const pathNumber = Number(petal.dataset.path); if (!Number.isFinite(pathNumber)) { return; } const path = tree.paths.find((entry) => entry.pathNumber === pathNumber); if (path) { renderPathDetail(path, tree, roseElements); } }; svg.addEventListener("click", (event) => { openPathFromTarget(event.target); }); svg.querySelectorAll(".kab-rose-petal[data-path]").forEach((petal) => { petal.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openPathFromTarget(petal); } }); }); } function renderRoseLandingIntro(roseElements) { if (typeof kabbalahDetailUi.renderRoseLandingIntro === "function") { kabbalahDetailUi.renderRoseLandingIntro(roseElements); } } function renderRoseCross(elements) { if (!state.tree || !elements?.roseCrossContainerEl) { return; } const roseElements = getRoseDetailElements(elements); if (!roseElements?.detailBodyEl) { return; } const roseBuilder = window.KabbalahRosicrucianCross?.buildRosicrucianCrossSVG; if (typeof roseBuilder !== "function") { return; } const roseSvg = roseBuilder(state.tree); elements.roseCrossContainerEl.innerHTML = ""; elements.roseCrossContainerEl.appendChild(roseSvg); bindRoseCrossInteractions(roseSvg, state.tree, roseElements); } 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 renderTree(elements) { if (!state.tree || !elements?.treeContainerEl) { return; } const svg = buildTreeSVG(state.tree); elements.treeContainerEl.innerHTML = ""; elements.treeContainerEl.appendChild(svg); bindTreeInteractions(svg, state.tree, 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 }; })();