(function () { "use strict"; 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(context, path) { const glyph = String(path?.hebrewLetter?.char || "").trim(); const pathNumber = Number(path?.pathNumber); const parts = []; if (context.state.showPathLetters && glyph) { parts.push(glyph); } if (context.state.showPathNumbers && Number.isFinite(pathNumber)) { parts.push(String(pathNumber)); } return parts.join(" "); } function svgEl(context, tag, attrs, text) { const el = document.createElementNS(context.NS, tag); for (const [key, value] of Object.entries(attrs || {})) { el.setAttribute(key, String(value)); } if (text != null) { el.textContent = text; } return el; } function buildTreeSVG(context) { const { tree, state, NODE_POS, SEPH_FILL, DARK_TEXT, DAAT, 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, R } = context; const svg = svgEl(context, "svg", { viewBox: "0 0 240 470", width: "100%", role: "img", "aria-label": "Kabbalah Tree of Life diagram", class: "kab-svg" }); svg.appendChild(svgEl(context, "rect", { x: 113, y: 30, width: 14, height: 420, rx: 7, fill: "#ffffff07", "pointer-events": "none" })); svg.appendChild(svgEl(context, "rect", { x: 33, y: 88, width: 14, height: 255, rx: 7, fill: "#ff220010", "pointer-events": "none" })); svg.appendChild(svgEl(context, "rect", { x: 193, y: 88, width: 14, height: 255, rx: 7, fill: "#2244ff10", "pointer-events": "none" })); [ { 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(context, "text", { x, y, "text-anchor": anchor, "dominant-baseline": "auto", fill: "#42425a", "font-size": "6", "pointer-events": "none" }, text)); }); 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(context, path); const hasLabel = Boolean(pathLabel); const labelY = hasTarotImage && hasLabel ? my - PATH_LABEL_OFFSET_WITH_TAROT : my; svg.appendChild(svgEl(context, "line", { x1, y1, x2, y2, class: "kab-path-line", "data-path": path.pathNumber, stroke: "#3c3c5c", "stroke-width": "1.5", "pointer-events": "none" })); svg.appendChild(svgEl(context, "line", { x1, y1, x2, y2, class: "kab-path-hit", "data-path": path.pathNumber, stroke: "transparent", "stroke-width": String(12 * context.PATH_MARKER_SCALE), role: "button", tabindex: "0", "aria-label": `Path ${path.pathNumber}: ${path.hebrewLetter?.transliteration || ""} — ${path.tarot?.card || ""}`, style: "cursor:pointer" })); if (hasLabel) { svg.appendChild(svgEl(context, "circle", { cx: mx, cy: labelY, r: PATH_LABEL_RADIUS.toFixed(2), fill: "#0d0d1c", opacity: "0.82", "pointer-events": "none" })); svg.appendChild(svgEl(context, "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(context, "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" })); } }); svg.appendChild(svgEl(context, "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(context, "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")); 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; svg.appendChild(svgEl(context, "circle", { cx, cy, r: "16", fill, opacity: "0.12", class: "kab-node-glow", "data-sephira": seph.number, "pointer-events": "none" })); svg.appendChild(svgEl(context, "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" })); svg.appendChild(svgEl(context, "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))); const lx = isLeft ? cx - R - 4 : cx + R + 4; svg.appendChild(svgEl(context, "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 bindTreeInteractions(context, svg) { const { tree, elements, renderSephiraDetail, renderPathDetail } = context; svg.addEventListener("click", (event) => { const clickTarget = event.target instanceof Element ? event.target : null; const sephNum = clickTarget?.dataset?.sephira; const pathNum = clickTarget?.dataset?.path; if (pathNum != null && clickTarget?.classList?.contains("kab-path-tarot")) { const path = tree.paths.find((entry) => entry.pathNumber === Number(pathNum)); if (path) { openTarotLightboxForPath(path, getSvgImageHref(clickTarget)); renderPathDetail(path, tree, elements); } return; } if (sephNum != null) { const seph = tree.sephiroth.find((entry) => entry.number === Number(sephNum)); if (seph) { renderSephiraDetail(seph, tree, elements); } } else if (pathNum != null) { const path = tree.paths.find((entry) => entry.pathNumber === Number(pathNum)); if (path) { renderPathDetail(path, tree, elements); } } }); svg.querySelectorAll(".kab-path-hit, .kab-path-tarot").forEach((element) => { element.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); const path = tree.paths.find((entry) => entry.pathNumber === Number(element.dataset.path)); if (path) { if (element.classList.contains("kab-path-tarot")) { openTarotLightboxForPath(path, getSvgImageHref(element)); } renderPathDetail(path, tree, elements); } } }); }); svg.querySelectorAll(".kab-node").forEach((element) => { element.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); const seph = tree.sephiroth.find((entry) => entry.number === Number(element.dataset.sephira)); if (seph) { renderSephiraDetail(seph, tree, elements); } } }); }); } function bindRoseCrossInteractions(context, svg, roseElements) { const { tree, renderPathDetail } = context; 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 renderRoseCross(context) { const { state, elements, getRoseDetailElements } = context; 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(context, roseSvg, roseElements); } function renderTree(context) { const { state, elements } = context; if (!state.tree || !elements?.treeContainerEl) { return; } const svg = buildTreeSVG(context); elements.treeContainerEl.innerHTML = ""; elements.treeContainerEl.appendChild(svg); bindTreeInteractions(context, svg); } window.KabbalahViewsUi = { renderTree, renderRoseCross }; })();