(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 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 PLANET_ID_TO_LABEL = { saturn: "Saturn", jupiter: "Jupiter", mars: "Mars", sol: "Sol", venus: "Venus", mercury: "Mercury", luna: "Luna" }; const MINOR_RANK_BY_PLURAL = { aces: "Ace", twos: "Two", threes: "Three", fours: "Four", fives: "Five", sixes: "Six", sevens: "Seven", eights: "Eight", nines: "Nine", tens: "Ten" }; const MINOR_SUITS = ["Wands", "Cups", "Swords", "Disks"]; 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"), }; } 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 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; } // ─── 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; } // ─── detail panel helpers ─────────────────────────────────────────────────── function metaCard(label, value, wide) { const card = document.createElement("div"); card.className = wide ? "planet-meta-card kab-wide-card" : "planet-meta-card"; card.innerHTML = `${label}

${value || "—"}

`; return card; } 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 createNavButton(label, eventName, detail) { const btn = document.createElement("button"); btn.type = "button"; btn.className = "kab-god-link"; btn.textContent = `${label} ↗`; btn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent(eventName, { detail })); }); return btn; } function appendLinkRow(card, buttons) { if (!buttons?.length) return; const row = document.createElement("div"); row.className = "kab-god-links"; buttons.forEach((button) => row.appendChild(button)); card.appendChild(row); } function buildPlanetLuminaryCard(planetValue) { const card = metaCard("Planet / Luminary", planetValue); const planetId = resolvePlanetId(planetValue); if (planetId) { appendLinkRow(card, [ createNavButton(`View ${PLANET_ID_TO_LABEL[planetId] || planetValue} in Planets`, "nav:planet", { planetId }) ]); return card; } const zodiacId = resolveZodiacId(planetValue); if (zodiacId) { appendLinkRow(card, [ createNavButton(`View ${zodiacId.charAt(0).toUpperCase() + zodiacId.slice(1)} in Zodiac`, "nav:zodiac", { signId: zodiacId }) ]); } return card; } function extractMinorRank(attribution) { const match = String(attribution || "").match(/\bthe\s+4\s+(aces|twos|threes|fours|fives|sixes|sevens|eights|nines|tens)\b/i); if (!match) return null; return MINOR_RANK_BY_PLURAL[(match[1] || "").toLowerCase()] || null; } function buildMinorTarotNames(attribution) { const rank = extractMinorRank(attribution); if (!rank) return []; return MINOR_SUITS.map((suit) => `${rank} of ${suit}`); } function buildTarotAttributionCard(attribution) { const card = metaCard("Tarot Attribution", attribution); const minorCards = buildMinorTarotNames(attribution); if (minorCards.length) { appendLinkRow(card, minorCards.map((cardName) => createNavButton(cardName, "nav:tarot-trump", { cardName }) )); } return card; } function buildAstrologyCard(astrology) { const astroText = astrology ? `${astrology.name} (${astrology.type})` : "—"; const card = metaCard("Astrology", astroText); if (astrology?.type === "planet") { const planetId = resolvePlanetId(astrology.name); if (planetId) { appendLinkRow(card, [ createNavButton(`View ${PLANET_ID_TO_LABEL[planetId] || astrology.name} in Planets`, "nav:planet", { planetId }) ]); } } else if (astrology?.type === "zodiac") { const signId = resolveZodiacId(astrology.name); if (signId) { appendLinkRow(card, [ createNavButton(`View ${signId.charAt(0).toUpperCase() + signId.slice(1)} in Zodiac`, "nav:zodiac", { signId }) ]); } } return card; } function buildConnectsCard(path, fromName, toName) { const card = metaCard("Connects", `${fromName} → ${toName}`); appendLinkRow(card, [ createNavButton(`View ${fromName}`, "nav:kabbalah-path", { pathNo: Number(path.connects.from) }), createNavButton(`View ${toName}`, "nav:kabbalah-path", { pathNo: Number(path.connects.to) }) ]); return card; } function buildHebrewLetterCard(letter) { const label = `${letter.char || ""} ${letter.transliteration || ""} — "${letter.meaning || ""}" (${letter.letterType || ""})`; const card = metaCard("Hebrew Letter", label); const hebrewLetterId = resolveHebrewLetterId(letter.transliteration || letter.char || ""); if (hebrewLetterId) { appendLinkRow(card, [ createNavButton(`View ${letter.transliteration || letter.char || "Letter"} in Alphabet`, "nav:alphabet", { alphabet: "hebrew", hebrewLetterId }) ]); } return card; } 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 buildFourWorldsCard(tree, activeLetterToken = "") { const activeToken = HEBREW_LETTER_ALIASES[normalizeLetterToken(activeLetterToken)] || normalizeLetterToken(activeLetterToken); const worldLayers = Array.isArray(state.fourWorldLayers) && state.fourWorldLayers.length ? state.fourWorldLayers : DEFAULT_FOUR_QABALISTIC_WORLD_LAYERS; const card = document.createElement("div"); card.className = "planet-meta-card kab-wide-card"; const title = document.createElement("strong"); title.textContent = "Four Qabalistic Worlds & Soul Layers"; card.appendChild(title); const stack = document.createElement("div"); stack.className = "cal-item-stack"; worldLayers.forEach((layer) => { const row = document.createElement("div"); row.className = "cal-item-row"; const isActive = Boolean(activeToken) && activeToken === layer.hebrewToken; const head = document.createElement("div"); head.className = "cal-item-head"; head.innerHTML = ` ${layer.slot}: ${layer.letterChar} — ${layer.world} ${layer.soulLayer} `; row.appendChild(head); const worldLine = document.createElement("div"); worldLine.className = "planet-text"; worldLine.textContent = `${layer.worldLayer} · ${layer.worldDescription}`; row.appendChild(worldLine); const soulLine = document.createElement("div"); soulLine.className = "planet-text"; soulLine.textContent = `${layer.soulLayer} — ${layer.soulTitle}: ${layer.soulDescription}`; row.appendChild(soulLine); const buttonRow = []; const hebrewLetterId = resolveHebrewLetterId(layer.hebrewToken); if (hebrewLetterId) { buttonRow.push( createNavButton(`View ${layer.letterChar} in Alphabet`, "nav:alphabet", { alphabet: "hebrew", hebrewLetterId }) ); } const linkedPath = findPathByHebrewToken(tree, layer.hebrewToken); if (linkedPath?.pathNumber != null) { buttonRow.push( createNavButton(`View Path ${linkedPath.pathNumber}`, "nav:kabbalah-path", { pathNo: Number(linkedPath.pathNumber) }) ); } appendLinkRow(row, buttonRow); if (isActive) { row.style.borderColor = "#818cf8"; } stack.appendChild(row); }); card.appendChild(stack); return card; } function splitCorrespondenceNames(value) { return String(value || "") .split(/,|;|·|\/|\bor\b|\band\b|\+/i) .map((item) => item.trim()) .filter(Boolean); } function uniqueNames(values) { const seen = new Set(); const output = []; values.forEach((name) => { const key = String(name || "").toLowerCase(); if (seen.has(key)) return; seen.add(key); output.push(name); }); return output; } function godLinksCard(label, names, pathNo, metaText) { const card = document.createElement("div"); card.className = "planet-meta-card"; const title = document.createElement("strong"); title.textContent = label; card.appendChild(title); if (metaText) { const meta = document.createElement("p"); meta.className = "planet-text kab-god-meta"; meta.textContent = metaText; card.appendChild(meta); } const row = document.createElement("div"); row.className = "kab-god-links"; names.forEach((name) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "kab-god-link"; btn.textContent = name; btn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:gods", { detail: { godName: name, pathNo: Number(pathNo) } })); }); row.appendChild(btn); }); card.appendChild(row); return card; } 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") .forEach(el => el.classList.remove("kab-path-active")); } // ─── helper: append divine correspondences from gods.json ───────────────────── function appendGodsCards(pathNo, elements) { const gd = state.godsData[String(pathNo)]; if (!gd) return; const hasAny = gd.greek || gd.roman || gd.egyptian || gd.egyptianPractical || gd.elohim || gd.archangel || gd.angelicOrder; if (!hasAny) return; const sep = document.createElement("div"); sep.className = "planet-meta-card kab-wide-card"; sep.innerHTML = `Divine Correspondences`; elements.detailBodyEl.appendChild(sep); const greekNames = uniqueNames(splitCorrespondenceNames(gd.greek)); const romanNames = uniqueNames(splitCorrespondenceNames(gd.roman)); const egyptNames = uniqueNames([ ...splitCorrespondenceNames(gd.egyptianPractical), ...splitCorrespondenceNames(gd.egyptian) ]); if (greekNames.length) { elements.detailBodyEl.appendChild(godLinksCard("Greek", greekNames, pathNo)); } if (romanNames.length) { elements.detailBodyEl.appendChild(godLinksCard("Roman", romanNames, pathNo)); } if (egyptNames.length) { elements.detailBodyEl.appendChild(godLinksCard("Egyptian", egyptNames, pathNo)); } if (gd.elohim) { const g = gd.elohim; const meta = `${g.hebrew}${g.meaning ? " — " + g.meaning : ""}`; elements.detailBodyEl.appendChild(godLinksCard( "God Name", uniqueNames(splitCorrespondenceNames(g.transliteration)), pathNo, meta )); } if (gd.archangel) { const a = gd.archangel; const meta = `${a.hebrew}`; elements.detailBodyEl.appendChild(godLinksCard( "Archangel", uniqueNames(splitCorrespondenceNames(a.transliteration)), pathNo, meta )); } if (gd.angelicOrder) { const o = gd.angelicOrder; elements.detailBodyEl.appendChild(metaCard( "Angelic Order", `${o.hebrew} ${o.transliteration}${o.meaning ? " — " + o.meaning : ""}` )); } } // ─── render sephira detail ─────────────────────────────────────────────────── 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")); elements.detailNameEl.textContent = `${seph.number} · ${seph.name}`; elements.detailSubEl.textContent = [seph.nameHebrew, seph.translation, seph.planet].filter(Boolean).join(" · "); elements.detailBodyEl.innerHTML = ""; elements.detailBodyEl.appendChild(buildFourWorldsCard(tree)); elements.detailBodyEl.appendChild(buildPlanetLuminaryCard(seph.planet)); elements.detailBodyEl.appendChild(metaCard("Intelligence", seph.intelligence)); elements.detailBodyEl.appendChild(buildTarotAttributionCard(seph.tarot)); if (seph.description) { elements.detailBodyEl.appendChild( metaCard(seph.name, seph.description, true) ); } // Quick-access chips for connected paths const connected = tree.paths.filter( p => p.connects.from === seph.number || p.connects.to === seph.number ); if (connected.length) { const card = document.createElement("div"); card.className = "planet-meta-card kab-wide-card"; const chips = connected.map(p => `` + `${p.hebrewLetter?.char || ""} ${p.pathNumber}` + `` ).join(""); card.innerHTML = `Connected Paths
${chips}
`; elements.detailBodyEl.appendChild(card); card.querySelectorAll(".kab-chip[data-path]").forEach(chip => { const handler = () => { const path = tree.paths.find(p => p.pathNumber === Number(chip.dataset.path)); if (path) renderPathDetail(path, tree, elements); }; chip.addEventListener("click", handler); chip.addEventListener("keydown", e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handler(); } }); }); } appendGodsCards(seph.number, elements); } // ─── render path detail ────────────────────────────────────────────────────── 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")); const letter = path.hebrewLetter || {}; const fromName = tree.sephiroth.find(s => s.number === path.connects.from)?.name || path.connects.from; const toName = tree.sephiroth.find(s => s.number === path.connects.to)?.name || path.connects.to; const astro = path.astrology ? `${path.astrology.name} (${path.astrology.type})` : "—"; const tarotStr = path.tarot?.card ? `${path.tarot.card}${path.tarot.trumpNumber != null ? " · Trump " + path.tarot.trumpNumber : ""}` : "—"; elements.detailNameEl.textContent = `Path ${path.pathNumber} · ${letter.char || ""} ${letter.transliteration || ""}`; elements.detailSubEl.textContent = [path.tarot?.card, astro].filter(Boolean).join(" · "); elements.detailBodyEl.innerHTML = ""; elements.detailBodyEl.appendChild(buildFourWorldsCard(tree, letter.transliteration || letter.char || "")); elements.detailBodyEl.appendChild(buildConnectsCard(path, fromName, toName)); elements.detailBodyEl.appendChild(buildHebrewLetterCard(letter)); elements.detailBodyEl.appendChild(buildAstrologyCard(path.astrology)); // Tarot card — clickable if a trump card is associated const tarotMetaCard = document.createElement("div"); tarotMetaCard.className = "planet-meta-card"; const tarotLabel = document.createElement("strong"); tarotLabel.textContent = "Tarot"; tarotMetaCard.appendChild(tarotLabel); if (path.tarot?.card && path.tarot.trumpNumber != null) { const tarotBtn = document.createElement("button"); tarotBtn.type = "button"; tarotBtn.className = "kab-tarot-link"; tarotBtn.textContent = `${path.tarot.card} · Trump ${path.tarot.trumpNumber}`; tarotBtn.title = "Open in Tarot section"; tarotBtn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("kab:view-trump", { detail: { trumpNumber: path.tarot.trumpNumber } })); }); tarotMetaCard.appendChild(tarotBtn); } else { const tarotP = document.createElement("p"); tarotP.className = "planet-text"; tarotP.textContent = tarotStr || "—"; tarotMetaCard.appendChild(tarotP); } elements.detailBodyEl.appendChild(tarotMetaCard); elements.detailBodyEl.appendChild(metaCard("Intelligence", path.intelligence)); elements.detailBodyEl.appendChild(metaCard("Pillar", path.pillar)); if (path.description) { const desc = document.createElement("div"); desc.className = "planet-meta-card kab-wide-card"; desc.innerHTML = `Path ${path.pathNumber} — Sefer Yetzirah` + `

${path.description.replace(/\n/g, "

")}

`; elements.detailBodyEl.appendChild(desc); } appendGodsCards(path.pathNumber, elements); } function bindTreeInteractions(svg, tree, elements) { // Delegate clicks via element's own data attributes svg.addEventListener("click", e => { const sephNum = e.target.dataset?.sephira; const pathNum = e.target.dataset?.path; 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) 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 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); } 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 }; })();