(function () { "use strict"; const state = { initialized: false, controlsBound: false, cube: null, hebrewLetters: null, kabbalahPathsByLetterId: new Map(), markerDisplayMode: "both", rotationX: 18, rotationY: -28, selectedNodeType: "wall", showConnectorLines: true, showPrimalPoint: true, selectedConnectorId: null, selectedWallId: null, selectedEdgeId: null, focusMode: false }; const CUBE_VERTICES = [ [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1], [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1] ]; const FACE_GEOMETRY = { north: [4, 5, 6, 7], south: [1, 0, 3, 2], east: [5, 1, 2, 6], west: [0, 4, 7, 3], above: [0, 1, 5, 4], below: [7, 6, 2, 3] }; const EDGE_GEOMETRY = [ [0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7] ]; const WALL_ORDER = ["north", "south", "east", "west", "above", "below"]; const EDGE_ORDER = [ "north-east", "south-east", "east-above", "east-below", "north-above", "north-below", "north-west", "south-west", "west-above", "west-below", "south-above", "south-below" ]; const EDGE_GEOMETRY_KEYS = [ "south-above", "south-east", "south-below", "south-west", "north-above", "north-east", "north-below", "north-west", "west-above", "east-above", "east-below", "west-below" ]; const CUBE_VIEW_CENTER = { x: 110, y: 108 }; const LOCAL_DIRECTION_ORDER = ["east", "south", "west", "north"]; const LOCAL_DIRECTION_RANK = { east: 0, south: 1, west: 2, north: 3 }; const LOCAL_DIRECTION_VIEW_MAP = { north: "east", east: "south", south: "west", west: "north" }; const MOTHER_CONNECTORS = [ { id: "above-below", fromWallId: "above", toWallId: "below", hebrewLetterId: "alef", name: "Above ↔ Below" }, { id: "east-west", fromWallId: "east", toWallId: "west", hebrewLetterId: "mem", name: "East ↔ West" }, { id: "south-north", fromWallId: "south", toWallId: "north", hebrewLetterId: "shin", name: "South ↔ North" } ]; const WALL_FRONT_ROTATIONS = { north: { x: 0, y: 0 }, south: { x: 0, y: 180 }, east: { x: 0, y: -90 }, west: { x: 0, y: 90 }, above: { x: -90, y: 0 }, below: { x: 90, y: 0 } }; const cubeDetailUi = window.CubeDetailUi || {}; const cubeChassisUi = window.CubeChassisUi || {}; const cubeMathHelpers = window.CubeMathHelpers || {}; const cubeSelectionHelpers = window.CubeSelectionHelpers || {}; function getElements() { return { cubeSectionEl: document.getElementById("cube-section"), cubeLayoutEl: document.getElementById("cube-layout"), viewContainerEl: document.getElementById("cube-view-container"), rotateLeftEl: document.getElementById("cube-rotate-left"), rotateRightEl: document.getElementById("cube-rotate-right"), rotateUpEl: document.getElementById("cube-rotate-up"), rotateDownEl: document.getElementById("cube-rotate-down"), rotateResetEl: document.getElementById("cube-rotate-reset"), focusToggleEl: document.getElementById("cube-focus-toggle"), markerModeEl: document.getElementById("cube-marker-mode"), connectorToggleEl: document.getElementById("cube-connector-toggle"), primalToggleEl: document.getElementById("cube-primal-toggle"), rotationReadoutEl: document.getElementById("cube-rotation-readout"), detailNameEl: document.getElementById("cube-detail-name"), detailSubEl: document.getElementById("cube-detail-sub"), detailBodyEl: document.getElementById("cube-detail-body") }; } function normalizeId(value) { return String(value || "").trim().toLowerCase(); } function normalizeLetterKey(value) { const key = normalizeId(value).replace(/[^a-z]/g, ""); const aliases = { aleph: "alef", beth: "bet", zain: "zayin", cheth: "het", chet: "het", daleth: "dalet", kaf: "kaf", kaph: "kaf", teth: "tet", peh: "pe", tzaddi: "tsadi", tzadi: "tsadi", tzade: "tsadi", tsaddi: "tsadi", qoph: "qof", taw: "tav", tau: "tav" }; return aliases[key] || key; } function asRecord(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : null; } function getWalls() { const walls = Array.isArray(state.cube?.walls) ? state.cube.walls : []; return walls .slice() .sort((left, right) => WALL_ORDER.indexOf(normalizeId(left?.id)) - WALL_ORDER.indexOf(normalizeId(right?.id))); } function getWallById(wallId) { const target = normalizeId(wallId); return getWalls().find((wall) => normalizeId(wall?.id) === target) || null; } function normalizeEdgeId(value) { return normalizeId(value).replace(/[\s_]+/g, "-"); } function formatEdgeName(edgeId) { return normalizeEdgeId(edgeId) .split("-") .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } function formatDirectionName(direction) { const key = normalizeId(direction); return key ? `${key.charAt(0).toUpperCase()}${key.slice(1)}` : ""; } function getEdges() { const configuredEdges = Array.isArray(state.cube?.edges) ? state.cube.edges : []; const byId = new Map( configuredEdges.map((edge) => [normalizeEdgeId(edge?.id), edge]) ); return EDGE_ORDER.map((edgeId) => { const configured = byId.get(edgeId); if (configured) { return configured; } return { id: edgeId, name: formatEdgeName(edgeId), walls: edgeId.split("-") }; }); } function getEdgeById(edgeId) { const target = normalizeEdgeId(edgeId); return getEdges().find((edge) => normalizeEdgeId(edge?.id) === target) || null; } function getEdgeWalls(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 normalizeEdgeId(edge?.id) .split("-") .map((wallId) => normalizeId(wallId)) .filter(Boolean) .slice(0, 2); } function getEdgesForWall(wallOrWallId) { const wallId = normalizeId(typeof wallOrWallId === "string" ? wallOrWallId : wallOrWallId?.id); return getEdges().filter((edge) => getEdgeWalls(edge).includes(wallId)); } function toFiniteNumber(value) { const numeric = Number(value); return Number.isFinite(numeric) ? numeric : null; } if (typeof cubeMathHelpers.createCubeMathHelpers !== "function") { throw new Error("CubeMathHelpers.createCubeMathHelpers is unavailable. Ensure app/ui-cube-math.js loads before app/ui-cube.js."); } if (typeof cubeSelectionHelpers.createCubeSelectionHelpers !== "function") { throw new Error("CubeSelectionHelpers.createCubeSelectionHelpers is unavailable. Ensure app/ui-cube-selection.js loads before app/ui-cube.js."); } function normalizeAngle(angle) { return cubeMathUi.normalizeAngle(angle); } function setRotation(nextX, nextY) { cubeMathUi.setRotation(nextX, nextY); } function snapRotationToWall(wallId) { cubeMathUi.snapRotationToWall(wallId); } function facePoint(quad, u, v) { return cubeMathUi.facePoint(quad, u, v); } function projectVerticesForRotation(rotationX, rotationY) { return cubeMathUi.projectVerticesForRotation(rotationX, rotationY); } function projectVertices() { return cubeMathUi.projectVertices(); } function getEdgeGeometryById(edgeId) { return cubeMathUi.getEdgeGeometryById(edgeId); } function getWallEdgeDirections(wallOrWallId) { return cubeMathUi.getWallEdgeDirections(wallOrWallId); } function getEdgeDirectionForWall(wallId, edgeId) { return cubeMathUi.getEdgeDirectionForWall(wallId, edgeId); } function getEdgeDirectionLabelForWall(wallId, edgeId) { return cubeMathUi.getEdgeDirectionLabelForWall(wallId, edgeId); } function rotateAndRender(deltaX, deltaY) { setRotation(state.rotationX + deltaX, state.rotationY + deltaY); render(getElements()); } function resetRotationAndRender() { setRotation(18, -28); render(getElements()); } function isKeyboardEditableTarget(target) { if (!(target instanceof HTMLElement)) { return false; } if (target.isContentEditable) { return true; } return ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName); } function isCubeFocusKeyboardModeActive(elements = getElements()) { return Boolean(state.focusMode) && elements?.cubeSectionEl instanceof HTMLElement && !elements.cubeSectionEl.hidden; } function syncFocusControls(elements) { if (elements?.cubeLayoutEl instanceof HTMLElement) { elements.cubeLayoutEl.classList.toggle("is-cube-focus", Boolean(state.focusMode)); } if (elements?.focusToggleEl instanceof HTMLButtonElement) { elements.focusToggleEl.setAttribute("aria-pressed", state.focusMode ? "true" : "false"); elements.focusToggleEl.textContent = state.focusMode ? "Show Full Cube" : "Focus Cube"; } } function bindRotationControls(elements) { if (state.controlsBound) { return; } elements.rotateLeftEl?.addEventListener("click", () => rotateAndRender(0, -9)); elements.rotateRightEl?.addEventListener("click", () => rotateAndRender(0, 9)); elements.rotateUpEl?.addEventListener("click", () => rotateAndRender(-9, 0)); elements.rotateDownEl?.addEventListener("click", () => rotateAndRender(9, 0)); elements.rotateResetEl?.addEventListener("click", resetRotationAndRender); elements.focusToggleEl?.addEventListener("click", () => { state.focusMode = !state.focusMode; syncFocusControls(getElements()); }); elements.markerModeEl?.addEventListener("change", (event) => { const nextMode = normalizeId(event?.target?.value); state.markerDisplayMode = ["both", "letter", "astro", "tarot"].includes(nextMode) ? nextMode : "both"; render(getElements()); }); if (elements.connectorToggleEl) { elements.connectorToggleEl.checked = state.showConnectorLines; elements.connectorToggleEl.addEventListener("change", () => { state.showConnectorLines = Boolean(elements.connectorToggleEl.checked); if (!state.showConnectorLines && state.selectedNodeType === "connector") { state.selectedNodeType = "wall"; state.selectedConnectorId = null; } render(getElements()); }); } if (elements.primalToggleEl) { elements.primalToggleEl.checked = state.showPrimalPoint; elements.primalToggleEl.addEventListener("change", () => { state.showPrimalPoint = Boolean(elements.primalToggleEl.checked); if (!state.showPrimalPoint && state.selectedNodeType === "center") { state.selectedNodeType = "wall"; } render(getElements()); }); } document.addEventListener("keydown", (event) => { if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) { return; } if (!isCubeFocusKeyboardModeActive(getElements())) { return; } if (isKeyboardEditableTarget(event.target)) { return; } switch (String(event.key || "").toLowerCase()) { case "a": event.preventDefault(); rotateAndRender(0, -9); break; case "d": event.preventDefault(); rotateAndRender(0, 9); break; case "w": event.preventDefault(); rotateAndRender(-9, 0); break; case "s": event.preventDefault(); rotateAndRender(9, 0); break; default: break; } }); state.controlsBound = true; } function getHebrewLetterSymbol(hebrewLetterId) { return cubeMathUi.getHebrewLetterSymbol(hebrewLetterId); } function getHebrewLetterName(hebrewLetterId) { return cubeMathUi.getHebrewLetterName(hebrewLetterId); } function getAstrologySymbol(type, name) { return cubeMathUi.getAstrologySymbol(type, name); } function getEdgeLetterId(edge) { return normalizeLetterKey(edge?.hebrewLetterId || edge?.associations?.hebrewLetterId); } function getWallFaceLetterId(wall) { return normalizeLetterKey(wall?.hebrewLetterId || wall?.associations?.hebrewLetterId); } function getWallFaceLetter(wall) { const hebrewLetterId = getWallFaceLetterId(wall); if (!hebrewLetterId) { return ""; } return getHebrewLetterSymbol(hebrewLetterId); } function getCubeCenterData() { const center = state.cube?.center; return center && typeof center === "object" ? center : null; } function getCenterLetterId(center = null) { return cubeMathUi.getCenterLetterId(center); } function getCenterLetterSymbol(center = null) { return cubeMathUi.getCenterLetterSymbol(center); } function getConnectorById(connectorId) { const target = normalizeId(connectorId); return MOTHER_CONNECTORS.find((entry) => normalizeId(entry?.id) === target) || null; } function getConnectorPathEntry(connector) { const letterId = normalizeLetterKey(connector?.hebrewLetterId); if (!letterId) { return null; } return state.kabbalahPathsByLetterId.get(letterId) || null; } function getEdgePathEntry(edge) { const hebrewLetterId = getEdgeLetterId(edge); if (!hebrewLetterId) { return null; } return state.kabbalahPathsByLetterId.get(hebrewLetterId) || null; } const cubeMathUi = cubeMathHelpers.createCubeMathHelpers({ state, CUBE_VERTICES, FACE_GEOMETRY, EDGE_GEOMETRY, EDGE_GEOMETRY_KEYS, CUBE_VIEW_CENTER, WALL_FRONT_ROTATIONS, LOCAL_DIRECTION_VIEW_MAP, normalizeId, normalizeLetterKey, normalizeEdgeId, formatDirectionName, getEdgesForWall, getEdgePathEntry, getEdgeLetterId, getCubeCenterData }); const cubeSelectionUi = cubeSelectionHelpers.createCubeSelectionHelpers({ state, normalizeId, normalizeEdgeId, normalizeLetterKey, toFiniteNumber, getWalls, getWallById, getEdges, getEdgeById, getEdgeWalls, getEdgesForWall, getEdgeLetterId, getWallFaceLetterId, getEdgePathEntry, getConnectorById, snapRotationToWall, render, getElements }); function getEdgeAstrologySymbol(edge) { return cubeMathUi.getEdgeAstrologySymbol(edge); } function getEdgeMarkerDisplay(edge) { return cubeMathUi.getEdgeMarkerDisplay(edge); } function getEdgeLetter(edge) { return cubeMathUi.getEdgeLetter(edge); } function getWallTarotCard(wall) { return toDisplayText(wall?.associations?.tarotCard || wall?.tarotCard); } function getEdgeTarotCard(edge) { const pathEntry = getEdgePathEntry(edge); return toDisplayText(pathEntry?.tarot?.card); } function getConnectorTarotCard(connector) { const pathEntry = getConnectorPathEntry(connector); return toDisplayText(pathEntry?.tarot?.card); } function getCenterTarotCard(center = null) { const entry = center || getCubeCenterData(); return toDisplayText(entry?.associations?.tarotCard || entry?.tarotCard); } function resolveCardImageUrl(cardName) { const name = toDisplayText(cardName); if (!name || typeof window.TarotCardImages?.resolveTarotCardImage !== "function") { return null; } return window.TarotCardImages.resolveTarotCardImage(name) || null; } function openTarotCardLightbox(cardName, fallbackSrc = "", fallbackLabel = "") { const openLightbox = window.TarotUiLightbox?.open; if (typeof openLightbox !== "function") { return false; } const src = toDisplayText(fallbackSrc) || resolveCardImageUrl(cardName); if (!src) { return false; } const label = toDisplayText(cardName) || toDisplayText(fallbackLabel) || "Tarot card"; openLightbox(src, label); return true; } function applyPlacement(placement) { return cubeSelectionUi.applyPlacement(placement); } function toDisplayText(value) { return String(value ?? "").trim(); } function renderFaceSvg(containerEl, walls) { if (typeof cubeChassisUi.renderFaceSvg !== "function") { if (containerEl) { containerEl.replaceChildren(); } return; } cubeChassisUi.renderFaceSvg({ state, containerEl, walls, normalizeId, projectVertices, FACE_GEOMETRY, facePoint, normalizeEdgeId, getEdges, getEdgesForWall, EDGE_GEOMETRY, EDGE_GEOMETRY_KEYS, formatEdgeName, getEdgeWalls, getElements, render, snapRotationToWall, getWallFaceLetter, getWallTarotCard, resolveCardImageUrl, openTarotCardLightbox, MOTHER_CONNECTORS, formatDirectionName, getConnectorTarotCard, getHebrewLetterSymbol, toDisplayText, CUBE_VIEW_CENTER, getEdgeMarkerDisplay, getEdgeTarotCard, getCubeCenterData, getCenterTarotCard, getCenterLetterSymbol }); } function selectEdgeById(edgeId, preferredWallId = "") { return cubeSelectionUi.selectEdgeById(edgeId, preferredWallId); } function renderDetail(elements, walls) { if (typeof cubeDetailUi.renderDetail !== "function") { if (elements?.detailNameEl) { elements.detailNameEl.textContent = "Cube data unavailable"; } if (elements?.detailSubEl) { elements.detailSubEl.textContent = "Cube detail renderer missing."; } if (elements?.detailBodyEl) { elements.detailBodyEl.innerHTML = ""; } return; } cubeDetailUi.renderDetail({ state, elements, walls, normalizeId, normalizeEdgeId, normalizeLetterKey, formatDirectionName, formatEdgeName, toFiniteNumber, getWallById, getEdgeById, getEdges, getEdgeWalls, getEdgesForWall, getWallEdgeDirections, getConnectorById, getConnectorPathEntry, getCubeCenterData, getCenterLetterId, getCenterLetterSymbol, getEdgeLetterId, getEdgeLetter, getEdgePathEntry, getEdgeAstrologySymbol, getWallFaceLetterId, getWallFaceLetter, getHebrewLetterName, getHebrewLetterSymbol, localDirectionOrder: LOCAL_DIRECTION_ORDER, localDirectionRank: LOCAL_DIRECTION_RANK, onSelectWall: selectWallById, onSelectEdge: selectEdgeById }); } function render(elements) { syncFocusControls(elements); if (elements?.markerModeEl) { elements.markerModeEl.value = state.markerDisplayMode; } if (elements?.connectorToggleEl) { elements.connectorToggleEl.checked = state.showConnectorLines; } if (elements?.primalToggleEl) { elements.primalToggleEl.checked = state.showPrimalPoint; } if (elements?.rotationReadoutEl) { elements.rotationReadoutEl.textContent = `X ${Math.round(state.rotationX)}° · Y ${Math.round(state.rotationY)}°`; } const walls = getWalls(); renderFaceSvg(elements.viewContainerEl, walls); renderDetail(elements, walls); } function ensureCubeSection(magickDataset) { const cubeData = magickDataset?.grouped?.kabbalah?.cube; const elements = getElements(); state.cube = cubeData || null; state.hebrewLetters = asRecord(magickDataset?.grouped?.hebrewLetters) || asRecord(magickDataset?.grouped?.alphabets?.hebrew) || null; const pathList = Array.isArray(magickDataset?.grouped?.kabbalah?.["kabbalah-tree"]?.paths) ? magickDataset.grouped.kabbalah["kabbalah-tree"].paths : []; const letterEntries = state.hebrewLetters && typeof state.hebrewLetters === "object" ? Object.values(state.hebrewLetters) : []; const letterIdsByChar = new Map( letterEntries .map((letterEntry) => [String(letterEntry?.letter?.he || "").trim(), normalizeLetterKey(letterEntry?.id)]) .filter(([character, letterId]) => Boolean(character) && Boolean(letterId)) ); state.kabbalahPathsByLetterId = new Map( pathList .map((pathEntry) => { const transliterationId = normalizeLetterKey(pathEntry?.hebrewLetter?.transliteration); const char = String(pathEntry?.hebrewLetter?.char || "").trim(); const charId = letterIdsByChar.get(char) || ""; return [charId || transliterationId, pathEntry]; }) .filter(([letterId]) => Boolean(letterId)) ); if (!state.selectedWallId) { state.selectedWallId = normalizeId(getWalls()[0]?.id); } const initialEdge = getEdgesForWall(state.selectedWallId)[0] || getEdges()[0] || null; if (!state.selectedEdgeId || !getEdgeById(state.selectedEdgeId)) { state.selectedEdgeId = normalizeEdgeId(initialEdge?.id); } bindRotationControls(elements); render(elements); state.initialized = true; } function selectWallById(wallId) { return cubeSelectionUi.selectWallById(wallId); } function selectConnectorById(connectorId) { return cubeSelectionUi.selectConnectorById(connectorId); } function selectCenterNode() { return cubeSelectionUi.selectCenterNode(); } function selectPlacement(criteria = {}) { return cubeSelectionUi.selectPlacement(criteria); } function selectByHebrewLetterId(hebrewLetterId) { return selectPlacement({ hebrewLetterId }); } function selectBySignId(signId) { return selectPlacement({ signId }); } function selectByPlanetId(planetId) { return selectPlacement({ planetId }); } function selectByPathNo(pathNo) { return selectPlacement({ pathNo }); } window.CubeSectionUi = { ensureCubeSection, selectWallById, selectPlacement, selectByHebrewLetterId, selectBySignId, selectByPlanetId, selectByPathNo, getEdgeDirectionForWall, getEdgeDirectionLabelForWall }; })();