(function () { "use strict"; function renderFaceSvg(context) { const { state, containerEl, walls, normalizeId, projectVertices, FACE_GEOMETRY, facePoint, normalizeEdgeId, getEdges, 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 } = context; 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(context.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; const cardH = 60; const wallTarotCard = getWallTarotCard(wall); const cardImg = document.createElementNS(svgNS, "image"); cardImg.setAttribute("class", "cube-tarot-image cube-face-card"); 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"); cardImg.setAttribute("aria-label", `Open ${wallTarotCard || (wall?.name || wallId)} card image`); cardImg.setAttribute("preserveAspectRatio", "xMidYMid meet"); cardImg.addEventListener("click", (event) => { event.stopPropagation(); selectWall(); openTarotCardLightbox(wallTarotCard, cardUrl, `${wall?.name || wallId} wall tarot card`); }); cardImg.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); event.stopPropagation(); selectWall(); openTarotCardLightbox(wallTarotCard, cardUrl, `${wall?.name || wallId} wall tarot card`); } }); 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); const selectConnector = () => { state.selectedNodeType = "connector"; state.selectedConnectorId = connectorId; render(getElements()); }; if (state.markerDisplayMode === "tarot" && connectorCardUrl) { const cardW = 18; const cardH = 27; const connectorTarotCard = getConnectorTarotCard(connector); const connectorImg = document.createElementNS(svgNS, "image"); connectorImg.setAttribute("class", "cube-tarot-image cube-connector-card"); 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)); connectorImg.setAttribute("role", "button"); connectorImg.setAttribute("tabindex", "0"); connectorImg.setAttribute("aria-label", `Open ${connectorTarotCard || connector?.name || "connector"} card image`); connectorImg.setAttribute("preserveAspectRatio", "xMidYMid meet"); 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"); } }); 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; const edgeTarotCard = getEdgeTarotCard(edge); const cardImg = document.createElementNS(svgNS, "image"); cardImg.setAttribute("class", `cube-tarot-image cube-direction-card${edgeIsActive ? " is-active" : ""}`); 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)); cardImg.setAttribute("role", "button"); cardImg.setAttribute("tabindex", "0"); cardImg.setAttribute("aria-label", `Open ${edgeTarotCard || edge?.name || "edge"} card image`); cardImg.setAttribute("preserveAspectRatio", "xMidYMid meet"); 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"); } }); 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; const centerTarotCard = getCenterTarotCard(center); const centerImg = document.createElementNS(svgNS, "image"); centerImg.setAttribute("class", "cube-tarot-image cube-center-card"); 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)); centerImg.setAttribute("role", "button"); centerImg.setAttribute("tabindex", "0"); centerImg.setAttribute("aria-label", `Open ${centerTarotCard || "Primal Point"} card image`); centerImg.setAttribute("preserveAspectRatio", "xMidYMid meet"); 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"); } }); 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); } window.CubeChassisUi = { renderFaceSvg }; })();