Files
TaroTime/app/ui-cube.js

1583 lines
52 KiB
JavaScript
Raw Normal View History

2026-03-07 01:09:00 -08:00
(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
};
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 }
};
2026-03-07 05:17:50 -08:00
const cubeDetailUi = window.CubeDetailUi || {};
2026-03-07 01:09:00 -08:00
function getElements() {
return {
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"),
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;
}
function normalizeAngle(angle) {
let next = angle;
while (next > 180) {
next -= 360;
}
while (next <= -180) {
next += 360;
}
return next;
}
function setRotation(nextX, nextY) {
state.rotationX = normalizeAngle(nextX);
state.rotationY = normalizeAngle(nextY);
}
function snapRotationToWall(wallId) {
const target = WALL_FRONT_ROTATIONS[normalizeId(wallId)];
if (!target) {
return;
}
setRotation(target.x, target.y);
}
function facePoint(quad, u, v) {
const weight0 = ((1 - u) * (1 - v)) / 4;
const weight1 = ((1 + u) * (1 - v)) / 4;
const weight2 = ((1 + u) * (1 + v)) / 4;
const weight3 = ((1 - u) * (1 + v)) / 4;
return {
x: quad[0].x * weight0 + quad[1].x * weight1 + quad[2].x * weight2 + quad[3].x * weight3,
y: quad[0].y * weight0 + quad[1].y * weight1 + quad[2].y * weight2 + quad[3].y * weight3
};
}
function projectVerticesForRotation(rotationX, rotationY) {
const yaw = (rotationY * Math.PI) / 180;
const pitch = (rotationX * Math.PI) / 180;
const cosY = Math.cos(yaw);
const sinY = Math.sin(yaw);
const cosX = Math.cos(pitch);
const sinX = Math.sin(pitch);
const centerX = CUBE_VIEW_CENTER.x;
const centerY = CUBE_VIEW_CENTER.y;
const scale = 54;
const camera = 4.6;
return CUBE_VERTICES.map(([x, y, z]) => {
const x1 = x * cosY + z * sinY;
const z1 = -x * sinY + z * cosY;
const y2 = y * cosX - z1 * sinX;
const z2 = y * sinX + z1 * cosX;
const perspective = camera / (camera - z2);
return {
x: centerX + x1 * scale * perspective,
y: centerY + y2 * scale * perspective,
z: z2
};
});
}
function projectVertices() {
return projectVerticesForRotation(state.rotationX, state.rotationY);
}
function getEdgeGeometryById(edgeId) {
const canonicalId = normalizeEdgeId(edgeId);
const geometryIndex = EDGE_GEOMETRY_KEYS.indexOf(canonicalId);
if (geometryIndex < 0) {
return null;
}
return EDGE_GEOMETRY[geometryIndex] || null;
}
function getWallEdgeDirections(wallOrWallId) {
const wallId = normalizeId(typeof wallOrWallId === "string" ? wallOrWallId : wallOrWallId?.id);
const faceIndices = FACE_GEOMETRY[wallId];
if (!Array.isArray(faceIndices) || faceIndices.length !== 4) {
return new Map();
}
const frontRotation = WALL_FRONT_ROTATIONS[wallId] || {
x: state.rotationX,
y: state.rotationY
};
const projectedVertices = projectVerticesForRotation(frontRotation.x, frontRotation.y);
const quad = faceIndices.map((index) => projectedVertices[index]);
const center = facePoint(quad, 0, 0);
const directionsByEdgeId = new Map();
getEdgesForWall(wallId).forEach((edge) => {
const geometry = getEdgeGeometryById(edge?.id);
if (!geometry) {
return;
}
const [fromIndex, toIndex] = geometry;
const from = projectedVertices[fromIndex];
const to = projectedVertices[toIndex];
if (!from || !to) {
return;
}
const midpointX = (from.x + to.x) / 2;
const midpointY = (from.y + to.y) / 2;
const dx = midpointX - center.x;
const dy = midpointY - center.y;
const directionByPosition = Math.abs(dx) >= Math.abs(dy)
? (dx >= 0 ? "east" : "west")
: (dy >= 0 ? "south" : "north");
const direction = LOCAL_DIRECTION_VIEW_MAP[directionByPosition] || directionByPosition;
directionsByEdgeId.set(normalizeEdgeId(edge?.id), direction);
});
return directionsByEdgeId;
}
function getEdgeDirectionForWall(wallId, edgeId) {
const wallKey = normalizeId(wallId);
const edgeKey = normalizeEdgeId(edgeId);
if (!wallKey || !edgeKey) {
return "";
}
const directions = getWallEdgeDirections(wallKey);
return directions.get(edgeKey) || "";
}
function getEdgeDirectionLabelForWall(wallId, edgeId) {
return formatDirectionName(getEdgeDirectionForWall(wallId, edgeId));
}
function bindRotationControls(elements) {
if (state.controlsBound) {
return;
}
const rotateAndRender = (deltaX, deltaY) => {
setRotation(state.rotationX + deltaX, state.rotationY + deltaY);
render(getElements());
};
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", () => {
setRotation(18, -28);
render(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());
});
}
state.controlsBound = true;
}
function getHebrewLetterSymbol(hebrewLetterId) {
const id = normalizeLetterKey(hebrewLetterId);
if (!id || !state.hebrewLetters) {
return "";
}
const entry = state.hebrewLetters[id];
if (!entry || typeof entry !== "object") {
return "";
}
const symbol = String(
entry?.letter?.he || entry?.he || entry?.glyph || entry?.symbol || ""
).trim();
return symbol;
}
function getHebrewLetterName(hebrewLetterId) {
const id = normalizeLetterKey(hebrewLetterId);
if (!id || !state.hebrewLetters) {
return "";
}
const entry = state.hebrewLetters[id];
if (!entry || typeof entry !== "object") {
return "";
}
const name = String(entry?.letter?.name || entry?.name || "").trim();
return name;
}
function getAstrologySymbol(type, name) {
const normalizedType = normalizeId(type);
const normalizedName = normalizeId(name);
const planetSymbols = {
mercury: "☿︎",
venus: "♀︎",
mars: "♂︎",
jupiter: "♃︎",
saturn: "♄︎",
sol: "☉︎",
sun: "☉︎",
luna: "☾︎",
moon: "☾︎",
earth: "⊕",
uranus: "♅︎",
neptune: "♆︎",
pluto: "♇︎"
};
const zodiacSymbols = {
aries: "♈︎",
taurus: "♉︎",
gemini: "♊︎",
cancer: "♋︎",
leo: "♌︎",
virgo: "♍︎",
libra: "♎︎",
scorpio: "♏︎",
sagittarius: "♐︎",
capricorn: "♑︎",
aquarius: "♒︎",
pisces: "♓︎"
};
const elementSymbols = {
fire: "🜂",
water: "🜄",
air: "🜁",
earth: "🜃",
spirit: "🜀"
};
if (normalizedType === "planet") {
return planetSymbols[normalizedName] || "";
}
if (normalizedType === "zodiac") {
return zodiacSymbols[normalizedName] || "";
}
if (normalizedType === "element") {
return elementSymbols[normalizedName] || "";
}
return "";
}
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) {
const entry = center || getCubeCenterData();
return normalizeLetterKey(entry?.hebrewLetterId || entry?.associations?.hebrewLetterId || entry?.letter);
}
function getCenterLetterSymbol(center = null) {
const centerLetterId = getCenterLetterId(center);
if (!centerLetterId) {
return "";
}
return getHebrewLetterSymbol(centerLetterId);
}
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;
}
function getEdgeAstrologySymbol(edge) {
const pathEntry = getEdgePathEntry(edge);
const astrology = pathEntry?.astrology || {};
return getAstrologySymbol(astrology.type, astrology.name);
}
function getEdgeMarkerDisplay(edge) {
const letter = getEdgeLetter(edge);
const astro = getEdgeAstrologySymbol(edge);
if (state.markerDisplayMode === "letter") {
return letter
? { text: letter, isMissing: false }
: { text: "!", isMissing: true };
}
if (state.markerDisplayMode === "astro") {
return astro
? { text: astro, isMissing: false }
: { text: "!", isMissing: true };
}
if (letter && astro) {
return { text: `${letter} ${astro}`, isMissing: false };
}
return { text: "!", isMissing: true };
}
function getEdgeLetter(edge) {
const hebrewLetterId = getEdgeLetterId(edge);
if (!hebrewLetterId) {
return "";
}
return getHebrewLetterSymbol(hebrewLetterId);
}
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;
}
2026-03-07 05:17:50 -08:00
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;
}
2026-03-07 01:09:00 -08:00
function applyPlacement(placement) {
const fallbackWallId = normalizeId(getWalls()[0]?.id);
const nextWallId = normalizeId(placement?.wallId || placement?.wall?.id || state.selectedWallId || fallbackWallId);
const wall = getWallById(nextWallId);
if (!wall) {
return false;
}
state.selectedWallId = normalizeId(wall.id);
const candidateEdgeId = normalizeEdgeId(placement?.edgeId || placement?.edge?.id);
const wallEdges = getEdgesForWall(state.selectedWallId);
const resolvedEdgeId = candidateEdgeId && getEdgeById(candidateEdgeId)
? candidateEdgeId
: normalizeEdgeId(wallEdges[0]?.id || getEdges()[0]?.id);
state.selectedEdgeId = resolvedEdgeId;
state.selectedNodeType = "wall";
state.selectedConnectorId = null;
render(getElements());
return true;
}
function toDisplayText(value) {
return String(value ?? "").trim();
}
function renderFaceSvg(containerEl, walls) {
if (!containerEl) {
return;
}
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("viewBox", "0 0 240 220");
svg.setAttribute("width", "100%");
svg.setAttribute("class", "cube-svg");
svg.setAttribute("role", "img");
svg.setAttribute("aria-label", "Cube of Space interactive chassis");
const wallById = new Map(walls.map((wall) => [normalizeId(wall?.id), wall]));
const projectedVertices = projectVertices();
const faces = Object.entries(FACE_GEOMETRY)
.map(([wallId, indices]) => {
const wall = wallById.get(wallId);
if (!wall) {
return null;
}
const quad = indices.map((index) => projectedVertices[index]);
const avgDepth = quad.reduce((sum, point) => sum + point.z, 0) / quad.length;
return {
wallId,
wall,
quad,
depth: avgDepth,
pointsText: quad.map((point) => `${point.x.toFixed(2)},${point.y.toFixed(2)}`).join(" ")
};
})
.filter(Boolean)
.sort((left, right) => left.depth - right.depth);
faces.forEach((faceData) => {
const { wallId, wall, quad, pointsText } = faceData;
const isActive = wallId === normalizeId(state.selectedWallId);
const polygon = document.createElementNS(svgNS, "polygon");
polygon.setAttribute("points", pointsText);
polygon.setAttribute("class", `cube-face${isActive ? " is-active" : ""}`);
polygon.setAttribute("fill", "#000");
polygon.setAttribute("fill-opacity", isActive ? "0.78" : "0.62");
polygon.setAttribute("stroke", "currentColor");
polygon.setAttribute("stroke-opacity", isActive ? "0.92" : "0.68");
polygon.setAttribute("stroke-width", isActive ? "2.5" : "1");
polygon.setAttribute("data-wall-id", wallId);
polygon.setAttribute("role", "button");
polygon.setAttribute("tabindex", "0");
polygon.setAttribute("aria-label", `Cube wall ${wall?.name || wallId}`);
const selectWall = () => {
state.selectedWallId = wallId;
state.selectedEdgeId = normalizeEdgeId(getEdgesForWall(wallId)[0]?.id || getEdges()[0]?.id);
state.selectedNodeType = "wall";
state.selectedConnectorId = null;
snapRotationToWall(wallId);
render(getElements());
};
polygon.addEventListener("click", selectWall);
polygon.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
selectWall();
}
});
svg.appendChild(polygon);
const wallFaceLetter = getWallFaceLetter(wall);
const faceGlyphAnchor = facePoint(quad, 0, 0);
if (state.markerDisplayMode === "tarot") {
const cardUrl = resolveCardImageUrl(getWallTarotCard(wall));
if (cardUrl) {
let defs = svg.querySelector("defs");
if (!defs) {
defs = document.createElementNS(svgNS, "defs");
svg.insertBefore(defs, svg.firstChild);
}
const clipId = `face-clip-${wallId}`;
const clipPath = document.createElementNS(svgNS, "clipPath");
clipPath.setAttribute("id", clipId);
const clipPoly = document.createElementNS(svgNS, "polygon");
clipPoly.setAttribute("points", pointsText);
clipPath.appendChild(clipPoly);
defs.appendChild(clipPath);
const cardW = 40, cardH = 60;
2026-03-07 05:17:50 -08:00
const wallTarotCard = getWallTarotCard(wall);
2026-03-07 01:09:00 -08:00
const cardImg = document.createElementNS(svgNS, "image");
2026-03-07 05:17:50 -08:00
cardImg.setAttribute("class", "cube-tarot-image cube-face-card");
2026-03-07 01:09:00 -08:00
cardImg.setAttribute("href", cardUrl);
cardImg.setAttribute("x", String((faceGlyphAnchor.x - cardW / 2).toFixed(2)));
cardImg.setAttribute("y", String((faceGlyphAnchor.y - cardH / 2).toFixed(2)));
cardImg.setAttribute("width", String(cardW));
cardImg.setAttribute("height", String(cardH));
cardImg.setAttribute("clip-path", `url(#${clipId})`);
cardImg.setAttribute("role", "button");
cardImg.setAttribute("tabindex", "0");
2026-03-07 05:17:50 -08:00
cardImg.setAttribute("aria-label", `Open ${wallTarotCard || (wall?.name || wallId)} card image`);
2026-03-07 01:09:00 -08:00
cardImg.setAttribute("preserveAspectRatio", "xMidYMid meet");
2026-03-07 05:17:50 -08:00
cardImg.addEventListener("click", (event) => {
event.stopPropagation();
selectWall();
openTarotCardLightbox(wallTarotCard, cardUrl, `${wall?.name || wallId} wall tarot card`);
});
2026-03-07 01:09:00 -08:00
cardImg.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
2026-03-07 05:17:50 -08:00
event.stopPropagation();
2026-03-07 01:09:00 -08:00
selectWall();
2026-03-07 05:17:50 -08:00
openTarotCardLightbox(wallTarotCard, cardUrl, `${wall?.name || wallId} wall tarot card`);
2026-03-07 01:09:00 -08:00
}
});
svg.appendChild(cardImg);
}
} else {
const faceGlyph = document.createElementNS(svgNS, "text");
faceGlyph.setAttribute(
"class",
`cube-face-symbol${isActive ? " is-active" : ""}${wallFaceLetter ? "" : " is-missing"}`
);
faceGlyph.setAttribute("x", String(faceGlyphAnchor.x));
faceGlyph.setAttribute("y", String(faceGlyphAnchor.y));
faceGlyph.setAttribute("text-anchor", "middle");
faceGlyph.setAttribute("dominant-baseline", "middle");
faceGlyph.setAttribute("pointer-events", "none");
faceGlyph.textContent = wallFaceLetter || "!";
svg.appendChild(faceGlyph);
}
const labelAnchor = facePoint(quad, 0, 0.9);
const label = document.createElementNS(svgNS, "text");
label.setAttribute("class", `cube-face-label${isActive ? " is-active" : ""}`);
label.setAttribute("x", String(labelAnchor.x));
label.setAttribute("y", String(labelAnchor.y));
label.setAttribute("text-anchor", "middle");
label.setAttribute("dominant-baseline", "middle");
label.setAttribute("pointer-events", "none");
label.textContent = wall?.name || wallId;
svg.appendChild(label);
});
const faceCenterByWallId = new Map(
faces.map((faceData) => [faceData.wallId, facePoint(faceData.quad, 0, 0)])
);
if (state.showConnectorLines) {
MOTHER_CONNECTORS.forEach((connector, connectorIndex) => {
const fromWallId = normalizeId(connector?.fromWallId);
const toWallId = normalizeId(connector?.toWallId);
const from = faceCenterByWallId.get(fromWallId);
const to = faceCenterByWallId.get(toWallId);
if (!from || !to) {
return;
}
const connectorId = normalizeId(connector?.id);
const isActive = state.selectedNodeType === "connector"
&& normalizeId(state.selectedConnectorId) === connectorId;
const connectorLetter = getHebrewLetterSymbol(connector?.hebrewLetterId);
const connectorCardUrl = state.markerDisplayMode === "tarot"
? resolveCardImageUrl(getConnectorTarotCard(connector))
: null;
const group = document.createElementNS(svgNS, "g");
group.setAttribute("class", `cube-connector${isActive ? " is-active" : ""}`);
group.setAttribute("role", "button");
group.setAttribute("tabindex", "0");
group.setAttribute(
"aria-label",
`Mother connector ${formatDirectionName(fromWallId)} to ${formatDirectionName(toWallId)}`
);
const connectorLine = document.createElementNS(svgNS, "line");
connectorLine.setAttribute("class", `cube-connector-line${isActive ? " is-active" : ""}`);
connectorLine.setAttribute("x1", from.x.toFixed(2));
connectorLine.setAttribute("y1", from.y.toFixed(2));
connectorLine.setAttribute("x2", to.x.toFixed(2));
connectorLine.setAttribute("y2", to.y.toFixed(2));
group.appendChild(connectorLine);
const connectorHit = document.createElementNS(svgNS, "line");
connectorHit.setAttribute("class", "cube-connector-hit");
connectorHit.setAttribute("x1", from.x.toFixed(2));
connectorHit.setAttribute("y1", from.y.toFixed(2));
connectorHit.setAttribute("x2", to.x.toFixed(2));
connectorHit.setAttribute("y2", to.y.toFixed(2));
group.appendChild(connectorHit);
const dx = to.x - from.x;
const dy = to.y - from.y;
const length = Math.hypot(dx, dy) || 1;
const perpX = -dy / length;
const perpY = dx / length;
const shift = (connectorIndex - 1) * 12;
const labelX = ((from.x + to.x) / 2) + (perpX * shift);
const labelY = ((from.y + to.y) / 2) + (perpY * shift);
2026-03-07 05:17:50 -08:00
const selectConnector = () => {
state.selectedNodeType = "connector";
state.selectedConnectorId = connectorId;
render(getElements());
};
2026-03-07 01:09:00 -08:00
if (state.markerDisplayMode === "tarot" && connectorCardUrl) {
const cardW = 18;
const cardH = 27;
2026-03-07 05:17:50 -08:00
const connectorTarotCard = getConnectorTarotCard(connector);
2026-03-07 01:09:00 -08:00
const connectorImg = document.createElementNS(svgNS, "image");
2026-03-07 05:17:50 -08:00
connectorImg.setAttribute("class", "cube-tarot-image cube-connector-card");
2026-03-07 01:09:00 -08:00
connectorImg.setAttribute("href", connectorCardUrl);
connectorImg.setAttribute("x", String((labelX - cardW / 2).toFixed(2)));
connectorImg.setAttribute("y", String((labelY - cardH / 2).toFixed(2)));
connectorImg.setAttribute("width", String(cardW));
connectorImg.setAttribute("height", String(cardH));
2026-03-07 05:17:50 -08:00
connectorImg.setAttribute("role", "button");
connectorImg.setAttribute("tabindex", "0");
connectorImg.setAttribute("aria-label", `Open ${connectorTarotCard || connector?.name || "connector"} card image`);
2026-03-07 01:09:00 -08:00
connectorImg.setAttribute("preserveAspectRatio", "xMidYMid meet");
2026-03-07 05:17:50 -08:00
connectorImg.addEventListener("click", (event) => {
event.stopPropagation();
selectConnector();
openTarotCardLightbox(connectorTarotCard, connectorCardUrl, connector?.name || "Mother connector");
});
connectorImg.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
event.stopPropagation();
selectConnector();
openTarotCardLightbox(connectorTarotCard, connectorCardUrl, connector?.name || "Mother connector");
}
});
2026-03-07 01:09:00 -08:00
group.appendChild(connectorImg);
} else {
const connectorText = document.createElementNS(svgNS, "text");
connectorText.setAttribute(
"class",
`cube-connector-symbol${isActive ? " is-active" : ""}${connectorLetter ? "" : " is-missing"}`
);
connectorText.setAttribute("x", String(labelX));
connectorText.setAttribute("y", String(labelY));
connectorText.setAttribute("text-anchor", "middle");
connectorText.setAttribute("dominant-baseline", "middle");
connectorText.setAttribute("pointer-events", "none");
connectorText.textContent = connectorLetter || "!";
group.appendChild(connectorText);
}
group.addEventListener("click", selectConnector);
group.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
selectConnector();
}
});
svg.appendChild(group);
});
}
const edgeById = new Map(
getEdges().map((edge) => [normalizeEdgeId(edge?.id), edge])
);
EDGE_GEOMETRY.forEach(([fromIndex, toIndex], edgeIndex) => {
const edgeId = EDGE_GEOMETRY_KEYS[edgeIndex];
const edge = edgeById.get(edgeId) || {
id: edgeId,
name: formatEdgeName(edgeId),
walls: edgeId.split("-")
};
const markerDisplay = getEdgeMarkerDisplay(edge);
const edgeWalls = getEdgeWalls(edge);
const wallIsActive = edgeWalls.includes(normalizeId(state.selectedWallId));
const edgeIsActive = normalizeEdgeId(state.selectedEdgeId) === edgeId;
const from = projectedVertices[fromIndex];
const to = projectedVertices[toIndex];
const line = document.createElementNS(svgNS, "line");
line.setAttribute("x1", from.x.toFixed(2));
line.setAttribute("y1", from.y.toFixed(2));
line.setAttribute("x2", to.x.toFixed(2));
line.setAttribute("y2", to.y.toFixed(2));
line.setAttribute("stroke", "currentColor");
line.setAttribute("stroke-opacity", edgeIsActive ? "0.94" : (wallIsActive ? "0.70" : "0.32"));
line.setAttribute("stroke-width", edgeIsActive ? "2.4" : (wallIsActive ? "1.9" : "1.4"));
line.setAttribute("class", `cube-edge-line${edgeIsActive ? " is-active" : ""}`);
line.setAttribute("role", "button");
line.setAttribute("tabindex", "0");
line.setAttribute("aria-label", `Cube edge ${toDisplayText(edge?.name) || formatEdgeName(edgeId)}`);
const selectEdge = () => {
state.selectedEdgeId = edgeId;
state.selectedNodeType = "wall";
state.selectedConnectorId = null;
if (!edgeWalls.includes(normalizeId(state.selectedWallId)) && edgeWalls[0]) {
state.selectedWallId = edgeWalls[0];
snapRotationToWall(state.selectedWallId);
}
render(getElements());
};
line.addEventListener("click", selectEdge);
line.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
selectEdge();
}
});
svg.appendChild(line);
const dx = to.x - from.x;
const dy = to.y - from.y;
const length = Math.hypot(dx, dy) || 1;
const normalX = -dy / length;
const normalY = dx / length;
const midpointX = (from.x + to.x) / 2;
const midpointY = (from.y + to.y) / 2;
const centerVectorX = midpointX - CUBE_VIEW_CENTER.x;
const centerVectorY = midpointY - CUBE_VIEW_CENTER.y;
const normalSign = (centerVectorX * normalX + centerVectorY * normalY) >= 0 ? 1 : -1;
const markerOffset = edgeIsActive ? 17 : (wallIsActive ? 13 : 12);
const labelX = midpointX + (normalX * markerOffset * normalSign);
const labelY = midpointY + (normalY * markerOffset * normalSign);
const marker = document.createElementNS(svgNS, "g");
marker.setAttribute(
"class",
`cube-direction${wallIsActive ? " is-wall-active" : ""}${edgeIsActive ? " is-active" : ""}`
);
marker.setAttribute("role", "button");
marker.setAttribute("tabindex", "0");
marker.setAttribute("aria-label", `Cube edge ${toDisplayText(edge?.name) || formatEdgeName(edgeId)}`);
if (state.markerDisplayMode === "tarot") {
const edgeCardUrl = resolveCardImageUrl(getEdgeTarotCard(edge));
if (edgeCardUrl) {
const cardW = edgeIsActive ? 28 : 20;
const cardH = edgeIsActive ? 42 : 30;
2026-03-07 05:17:50 -08:00
const edgeTarotCard = getEdgeTarotCard(edge);
2026-03-07 01:09:00 -08:00
const cardImg = document.createElementNS(svgNS, "image");
2026-03-07 05:17:50 -08:00
cardImg.setAttribute("class", `cube-tarot-image cube-direction-card${edgeIsActive ? " is-active" : ""}`);
2026-03-07 01:09:00 -08:00
cardImg.setAttribute("href", edgeCardUrl);
cardImg.setAttribute("x", String((labelX - cardW / 2).toFixed(2)));
cardImg.setAttribute("y", String((labelY - cardH / 2).toFixed(2)));
cardImg.setAttribute("width", String(cardW));
cardImg.setAttribute("height", String(cardH));
2026-03-07 05:17:50 -08:00
cardImg.setAttribute("role", "button");
cardImg.setAttribute("tabindex", "0");
cardImg.setAttribute("aria-label", `Open ${edgeTarotCard || edge?.name || "edge"} card image`);
2026-03-07 01:09:00 -08:00
cardImg.setAttribute("preserveAspectRatio", "xMidYMid meet");
2026-03-07 05:17:50 -08:00
cardImg.addEventListener("click", (event) => {
event.stopPropagation();
selectEdge();
openTarotCardLightbox(edgeTarotCard, edgeCardUrl, edge?.name || "Cube edge");
});
cardImg.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
event.stopPropagation();
selectEdge();
openTarotCardLightbox(edgeTarotCard, edgeCardUrl, edge?.name || "Cube edge");
}
});
2026-03-07 01:09:00 -08:00
marker.appendChild(cardImg);
} else {
const markerText = document.createElementNS(svgNS, "text");
markerText.setAttribute("class", "cube-direction-letter is-missing");
markerText.setAttribute("x", String(labelX));
markerText.setAttribute("y", String(labelY));
markerText.setAttribute("text-anchor", "middle");
markerText.setAttribute("dominant-baseline", "middle");
markerText.textContent = "!";
marker.appendChild(markerText);
}
} else {
const markerText = document.createElementNS(svgNS, "text");
markerText.setAttribute(
"class",
`cube-direction-letter${markerDisplay.isMissing ? " is-missing" : ""}`
);
markerText.setAttribute("x", String(labelX));
markerText.setAttribute("y", String(labelY));
markerText.setAttribute("text-anchor", "middle");
markerText.setAttribute("dominant-baseline", "middle");
markerText.textContent = markerDisplay.text;
marker.appendChild(markerText);
}
marker.addEventListener("click", selectEdge);
marker.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
selectEdge();
}
});
svg.appendChild(marker);
});
const center = getCubeCenterData();
if (center && state.showPrimalPoint) {
const centerLetter = getCenterLetterSymbol(center);
const centerCardUrl = state.markerDisplayMode === "tarot"
? resolveCardImageUrl(getCenterTarotCard(center))
: null;
const centerActive = state.selectedNodeType === "center";
const centerMarker = document.createElementNS(svgNS, "g");
centerMarker.setAttribute("class", `cube-center${centerActive ? " is-active" : ""}`);
centerMarker.setAttribute("role", "button");
centerMarker.setAttribute("tabindex", "0");
centerMarker.setAttribute("aria-label", "Cube primal point");
const centerHit = document.createElementNS(svgNS, "circle");
centerHit.setAttribute("class", "cube-center-hit");
centerHit.setAttribute("cx", String(CUBE_VIEW_CENTER.x));
centerHit.setAttribute("cy", String(CUBE_VIEW_CENTER.y));
centerHit.setAttribute("r", "18");
centerMarker.appendChild(centerHit);
if (state.markerDisplayMode === "tarot" && centerCardUrl) {
const cardW = 24;
const cardH = 36;
2026-03-07 05:17:50 -08:00
const centerTarotCard = getCenterTarotCard(center);
2026-03-07 01:09:00 -08:00
const centerImg = document.createElementNS(svgNS, "image");
2026-03-07 05:17:50 -08:00
centerImg.setAttribute("class", "cube-tarot-image cube-center-card");
2026-03-07 01:09:00 -08:00
centerImg.setAttribute("href", centerCardUrl);
centerImg.setAttribute("x", String((CUBE_VIEW_CENTER.x - cardW / 2).toFixed(2)));
centerImg.setAttribute("y", String((CUBE_VIEW_CENTER.y - cardH / 2).toFixed(2)));
centerImg.setAttribute("width", String(cardW));
centerImg.setAttribute("height", String(cardH));
2026-03-07 05:17:50 -08:00
centerImg.setAttribute("role", "button");
centerImg.setAttribute("tabindex", "0");
centerImg.setAttribute("aria-label", `Open ${centerTarotCard || "Primal Point"} card image`);
2026-03-07 01:09:00 -08:00
centerImg.setAttribute("preserveAspectRatio", "xMidYMid meet");
2026-03-07 05:17:50 -08:00
centerImg.addEventListener("click", (event) => {
event.stopPropagation();
state.selectedNodeType = "center";
state.selectedConnectorId = null;
render(getElements());
openTarotCardLightbox(centerTarotCard, centerCardUrl, "Primal Point");
});
centerImg.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
event.stopPropagation();
state.selectedNodeType = "center";
state.selectedConnectorId = null;
render(getElements());
openTarotCardLightbox(centerTarotCard, centerCardUrl, "Primal Point");
}
});
2026-03-07 01:09:00 -08:00
centerMarker.appendChild(centerImg);
} else {
const centerText = document.createElementNS(svgNS, "text");
centerText.setAttribute(
"class",
`cube-center-symbol${centerActive ? " is-active" : ""}${centerLetter ? "" : " is-missing"}`
);
centerText.setAttribute("x", String(CUBE_VIEW_CENTER.x));
centerText.setAttribute("y", String(CUBE_VIEW_CENTER.y));
centerText.setAttribute("text-anchor", "middle");
centerText.setAttribute("dominant-baseline", "middle");
centerText.setAttribute("pointer-events", "none");
centerText.textContent = centerLetter || "!";
centerMarker.appendChild(centerText);
}
const selectCenter = () => {
state.selectedNodeType = "center";
state.selectedConnectorId = null;
render(getElements());
};
centerMarker.addEventListener("click", selectCenter);
centerMarker.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
selectCenter();
}
});
svg.appendChild(centerMarker);
}
if (state.markerDisplayMode === "tarot") {
Array.from(svg.querySelectorAll("g.cube-direction")).forEach((group) => {
svg.appendChild(group);
});
if (state.showConnectorLines) {
Array.from(svg.querySelectorAll("g.cube-connector")).forEach((group) => {
svg.appendChild(group);
});
}
}
containerEl.replaceChildren(svg);
}
2026-03-07 05:17:50 -08:00
function selectEdgeById(edgeId, preferredWallId = "") {
const edge = getEdgeById(edgeId);
if (!edge) {
2026-03-07 01:09:00 -08:00
return false;
}
2026-03-07 05:17:50 -08:00
const currentWallId = normalizeId(state.selectedWallId);
const preferredId = normalizeId(preferredWallId);
const edgeWalls = getEdgeWalls(edge);
const nextWallId = preferredId && edgeWalls.includes(preferredId)
? preferredId
: (edgeWalls.includes(currentWallId) ? currentWallId : (edgeWalls[0] || currentWallId));
2026-03-07 01:09:00 -08:00
2026-03-07 05:17:50 -08:00
state.selectedEdgeId = normalizeEdgeId(edge.id);
state.selectedNodeType = "wall";
state.selectedConnectorId = null;
2026-03-07 01:09:00 -08:00
2026-03-07 05:17:50 -08:00
if (nextWallId) {
if (nextWallId !== currentWallId) {
state.selectedWallId = nextWallId;
snapRotationToWall(nextWallId);
} else if (!state.selectedWallId) {
state.selectedWallId = nextWallId;
}
2026-03-07 01:09:00 -08:00
}
2026-03-07 05:17:50 -08:00
render(getElements());
2026-03-07 01:09:00 -08:00
return true;
}
function renderDetail(elements, walls) {
2026-03-07 05:17:50 -08:00
if (typeof cubeDetailUi.renderDetail !== "function") {
2026-03-07 01:09:00 -08:00
if (elements?.detailNameEl) {
elements.detailNameEl.textContent = "Cube data unavailable";
}
if (elements?.detailSubEl) {
2026-03-07 05:17:50 -08:00
elements.detailSubEl.textContent = "Cube detail renderer missing.";
2026-03-07 01:09:00 -08:00
}
if (elements?.detailBodyEl) {
elements.detailBodyEl.innerHTML = "";
}
return;
}
2026-03-07 05:17:50 -08:00
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
2026-03-07 01:09:00 -08:00
});
}
function render(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) {
if (!state.initialized) {
return false;
}
const wall = getWallById(wallId);
if (!wall) {
return false;
}
state.selectedWallId = normalizeId(wall.id);
state.selectedEdgeId = normalizeEdgeId(getEdgesForWall(state.selectedWallId)[0]?.id || getEdges()[0]?.id);
state.selectedNodeType = "wall";
state.selectedConnectorId = null;
snapRotationToWall(state.selectedWallId);
render(getElements());
return true;
}
function selectConnectorById(connectorId) {
if (!state.initialized) {
return false;
}
const connector = getConnectorById(connectorId);
if (!connector) {
return false;
}
const fromWallId = normalizeId(connector.fromWallId);
if (fromWallId && getWallById(fromWallId)) {
state.selectedWallId = fromWallId;
state.selectedEdgeId = normalizeEdgeId(getEdgesForWall(fromWallId)[0]?.id || getEdges()[0]?.id);
snapRotationToWall(fromWallId);
}
state.showConnectorLines = true;
state.selectedNodeType = "connector";
state.selectedConnectorId = normalizeId(connector.id);
render(getElements());
return true;
}
function selectCenterNode() {
if (!state.initialized) {
return false;
}
state.showPrimalPoint = true;
state.selectedNodeType = "center";
state.selectedConnectorId = null;
render(getElements());
return true;
}
function selectPlacement(criteria = {}) {
if (!state.initialized) {
return false;
}
const wallId = normalizeId(criteria.wallId);
const connectorId = normalizeId(criteria.connectorId);
const edgeId = normalizeEdgeId(criteria.edgeId || criteria.directionId);
const hebrewLetterId = normalizeLetterKey(criteria.hebrewLetterId);
const signId = normalizeId(criteria.signId || criteria.zodiacSignId);
const planetId = normalizeId(criteria.planetId);
const pathNo = toFiniteNumber(criteria.pathNo || criteria.kabbalahPathNumber);
const trumpNo = toFiniteNumber(criteria.trumpNumber || criteria.tarotTrumpNumber);
const nodeType = normalizeId(criteria.nodeType);
const centerRequested = nodeType === "center"
|| Boolean(criteria.center)
|| Boolean(criteria.primalPoint)
|| normalizeId(criteria.centerId) === "primal-point";
const edges = getEdges();
const findEdgeBy = (predicate) => edges.find((edge) => predicate(edge)) || null;
const findWallForEdge = (edge, preferredWallId) => {
const edgeWalls = getEdgeWalls(edge);
if (preferredWallId && edgeWalls.includes(preferredWallId)) {
return preferredWallId;
}
return edgeWalls[0] || normalizeId(getWalls()[0]?.id);
};
if (connectorId) {
return selectConnectorById(connectorId);
}
if (centerRequested) {
return selectCenterNode();
}
if (edgeId) {
const edge = getEdgeById(edgeId);
if (!edge) {
return false;
}
return applyPlacement({
wallId: findWallForEdge(edge, wallId),
edgeId
});
}
if (wallId) {
const wall = getWallById(wallId);
if (!wall) {
return false;
}
// if an explicit edge id was not provided (or was empty) we treat this
// as a request to show the wall/face itself rather than any particular
// edge direction. `applyPlacement` only knows how to highlight edges,
// so we fall back to selecting the wall directly in that case. this
// is the behaviour we want when navigating from a "face" letter like
// dalet, where the placement computed by ui-alphabet leaves edgeId
// blank.
if (!edgeId) {
return selectWallById(wallId);
}
const firstEdge = getEdgesForWall(wallId)[0] || null;
return applyPlacement({ wallId, edgeId: firstEdge?.id });
}
if (hebrewLetterId) {
const byHebrew = findEdgeBy((edge) => getEdgeLetterId(edge) === hebrewLetterId);
if (byHebrew) {
return applyPlacement({
wallId: findWallForEdge(byHebrew),
edgeId: byHebrew.id
});
}
const byWallFace = getWalls().find((wall) => getWallFaceLetterId(wall) === hebrewLetterId) || null;
if (byWallFace) {
const byWallFaceId = normalizeId(byWallFace.id);
const firstEdge = getEdgesForWall(byWallFaceId)[0] || null;
return applyPlacement({ wallId: byWallFaceId, edgeId: firstEdge?.id });
}
}
if (signId) {
const bySign = findEdgeBy((edge) => {
const astrology = getEdgePathEntry(edge)?.astrology || {};
return normalizeId(astrology.type) === "zodiac" && normalizeId(astrology.name) === signId;
});
if (bySign) {
return applyPlacement({
wallId: findWallForEdge(bySign),
edgeId: bySign.id
});
}
}
if (pathNo != null) {
const byPath = findEdgeBy((edge) => toFiniteNumber(getEdgePathEntry(edge)?.pathNumber) === pathNo);
if (byPath) {
return applyPlacement({
wallId: findWallForEdge(byPath),
edgeId: byPath.id
});
}
}
if (trumpNo != null) {
const byTrump = findEdgeBy((edge) => {
const tarot = getEdgePathEntry(edge)?.tarot || {};
return toFiniteNumber(tarot.trumpNumber) === trumpNo;
});
if (byTrump) {
return applyPlacement({
wallId: findWallForEdge(byTrump),
edgeId: byTrump.id
});
}
}
if (planetId) {
const wall = getWalls().find((entry) => normalizeId(entry?.associations?.planetId) === planetId);
if (wall) {
const wallIdByPlanet = normalizeId(wall.id);
return applyPlacement({
wallId: wallIdByPlanet,
edgeId: getEdgesForWall(wallIdByPlanet)[0]?.id
});
}
}
return false;
}
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
};
})();