591 lines
22 KiB
JavaScript
591 lines
22 KiB
JavaScript
|
|
/* ui-zodiac.js — Zodiac sign browser section */
|
|||
|
|
(function () {
|
|||
|
|
"use strict";
|
|||
|
|
|
|||
|
|
const ELEMENT_STYLE = {
|
|||
|
|
fire: { emoji: "🔥", badge: "zod-badge--fire", label: "Fire" },
|
|||
|
|
earth: { emoji: "🌍", badge: "zod-badge--earth", label: "Earth" },
|
|||
|
|
air: { emoji: "💨", badge: "zod-badge--air", label: "Air" },
|
|||
|
|
water: { emoji: "💧", badge: "zod-badge--water", label: "Water" }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const PLANET_SYMBOLS = {
|
|||
|
|
saturn: "♄︎", jupiter: "♃︎", mars: "♂︎", sol: "☉︎",
|
|||
|
|
venus: "♀︎", mercury: "☿︎", luna: "☾︎"
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const MONTH_NAMES = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|||
|
|
|
|||
|
|
const state = {
|
|||
|
|
initialized: false,
|
|||
|
|
entries: [],
|
|||
|
|
filteredEntries: [],
|
|||
|
|
selectedId: null,
|
|||
|
|
searchQuery: "",
|
|||
|
|
kabPaths: [],
|
|||
|
|
decansBySign: {},
|
|||
|
|
monthRefsBySignId: new Map(),
|
|||
|
|
cubePlacementBySignId: new Map()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ── Elements ──────────────────────────────────────────────────────────
|
|||
|
|
function getElements() {
|
|||
|
|
return {
|
|||
|
|
listEl: document.getElementById("zodiac-sign-list"),
|
|||
|
|
countEl: document.getElementById("zodiac-sign-count"),
|
|||
|
|
searchEl: document.getElementById("zodiac-search-input"),
|
|||
|
|
searchClearEl: document.getElementById("zodiac-search-clear"),
|
|||
|
|
detailNameEl: document.getElementById("zodiac-detail-name"),
|
|||
|
|
detailSubEl: document.getElementById("zodiac-detail-sub"),
|
|||
|
|
detailBodyEl: document.getElementById("zodiac-detail-body")
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Normalise ─────────────────────────────────────────────────────────
|
|||
|
|
function norm(s) {
|
|||
|
|
return String(s || "").toLowerCase().replace(/[^a-z0-9 ]/g, "").trim();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function cap(s) {
|
|||
|
|
return String(s || "").charAt(0).toUpperCase() + String(s || "").slice(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildSearchText(sign) {
|
|||
|
|
return norm([
|
|||
|
|
sign.name?.en, sign.meaning?.en, sign.elementId, sign.quadruplicity,
|
|||
|
|
sign.planetId, sign.id
|
|||
|
|
].join(" "));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function formatDateRange(rulesFrom) {
|
|||
|
|
if (!Array.isArray(rulesFrom) || rulesFrom.length < 2) return "—";
|
|||
|
|
const [from, to] = rulesFrom;
|
|||
|
|
const fMonth = MONTH_NAMES[(from[0] || 1) - 1];
|
|||
|
|
const tMonth = MONTH_NAMES[(to[0] || 1) - 1];
|
|||
|
|
return `${fMonth} ${from[1]} – ${tMonth} ${to[1]}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildMonthReferencesBySign(referenceData) {
|
|||
|
|
const map = new Map();
|
|||
|
|
const months = Array.isArray(referenceData?.calendarMonths) ? referenceData.calendarMonths : [];
|
|||
|
|
const holidays = Array.isArray(referenceData?.celestialHolidays) ? referenceData.celestialHolidays : [];
|
|||
|
|
const signs = Array.isArray(referenceData?.signs) ? referenceData.signs : [];
|
|||
|
|
const monthById = new Map(months.map((month) => [month.id, month]));
|
|||
|
|
const monthByOrder = new Map(
|
|||
|
|
months
|
|||
|
|
.filter((month) => Number.isFinite(Number(month?.order)))
|
|||
|
|
.map((month) => [Number(month.order), month])
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
function parseMonthDay(value) {
|
|||
|
|
const [month, day] = String(value || "").split("-").map((part) => Number(part));
|
|||
|
|
if (!Number.isFinite(month) || !Number.isFinite(day)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return { month, day };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function monthOrdersInRange(startMonth, endMonth) {
|
|||
|
|
const orders = [];
|
|||
|
|
let cursor = startMonth;
|
|||
|
|
let guard = 0;
|
|||
|
|
|
|||
|
|
while (guard < 13) {
|
|||
|
|
orders.push(cursor);
|
|||
|
|
if (cursor === endMonth) {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
cursor = cursor === 12 ? 1 : cursor + 1;
|
|||
|
|
guard += 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return orders;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function pushRef(signId, month) {
|
|||
|
|
const key = String(signId || "").trim().toLowerCase();
|
|||
|
|
if (!key || !month?.id) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!map.has(key)) {
|
|||
|
|
map.set(key, []);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rows = map.get(key);
|
|||
|
|
if (rows.some((entry) => entry.id === month.id)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
rows.push({
|
|||
|
|
id: month.id,
|
|||
|
|
name: month.name || month.id,
|
|||
|
|
order: Number.isFinite(Number(month.order)) ? Number(month.order) : 999
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
months.forEach((month) => {
|
|||
|
|
pushRef(month?.associations?.zodiacSignId, month);
|
|||
|
|
const events = Array.isArray(month?.events) ? month.events : [];
|
|||
|
|
events.forEach((event) => {
|
|||
|
|
pushRef(event?.associations?.zodiacSignId, month);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
holidays.forEach((holiday) => {
|
|||
|
|
const month = monthById.get(holiday?.monthId);
|
|||
|
|
if (!month) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
pushRef(holiday?.associations?.zodiacSignId, month);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Structural month coverage from sign date ranges (e.g., Scorpio spans Oct+Nov).
|
|||
|
|
signs.forEach((sign) => {
|
|||
|
|
const start = parseMonthDay(sign?.start);
|
|||
|
|
const end = parseMonthDay(sign?.end);
|
|||
|
|
if (!start || !end || !sign?.id) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
monthOrdersInRange(start.month, end.month).forEach((monthOrder) => {
|
|||
|
|
const month = monthByOrder.get(monthOrder);
|
|||
|
|
if (month) {
|
|||
|
|
pushRef(sign.id, month);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
map.forEach((rows, key) => {
|
|||
|
|
rows.sort((left, right) => left.order - right.order || left.name.localeCompare(right.name));
|
|||
|
|
map.set(key, rows);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return map;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildCubeSignPlacements(magickDataset) {
|
|||
|
|
const placements = new Map();
|
|||
|
|
const cube = magickDataset?.grouped?.kabbalah?.cube || {};
|
|||
|
|
const walls = Array.isArray(cube?.walls)
|
|||
|
|
? cube.walls
|
|||
|
|
: [];
|
|||
|
|
const edges = Array.isArray(cube?.edges)
|
|||
|
|
? cube.edges
|
|||
|
|
: [];
|
|||
|
|
const paths = Array.isArray(magickDataset?.grouped?.kabbalah?.["kabbalah-tree"]?.paths)
|
|||
|
|
? magickDataset.grouped.kabbalah["kabbalah-tree"].paths
|
|||
|
|
: [];
|
|||
|
|
|
|||
|
|
function normalizeLetterId(value) {
|
|||
|
|
const key = String(value || "").toLowerCase().replace(/[^a-z]/g, "").trim();
|
|||
|
|
const aliases = {
|
|||
|
|
aleph: "alef",
|
|||
|
|
beth: "bet",
|
|||
|
|
zain: "zayin",
|
|||
|
|
cheth: "het",
|
|||
|
|
chet: "het",
|
|||
|
|
daleth: "dalet",
|
|||
|
|
teth: "tet",
|
|||
|
|
peh: "pe",
|
|||
|
|
tzaddi: "tsadi",
|
|||
|
|
tzadi: "tsadi",
|
|||
|
|
tzade: "tsadi",
|
|||
|
|
tsaddi: "tsadi",
|
|||
|
|
qoph: "qof",
|
|||
|
|
taw: "tav",
|
|||
|
|
tau: "tav"
|
|||
|
|
};
|
|||
|
|
return aliases[key] || key;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function edgeWalls(edge) {
|
|||
|
|
const explicitWalls = Array.isArray(edge?.walls)
|
|||
|
|
? edge.walls.map((wallId) => String(wallId || "").trim().toLowerCase()).filter(Boolean)
|
|||
|
|
: [];
|
|||
|
|
|
|||
|
|
if (explicitWalls.length >= 2) {
|
|||
|
|
return explicitWalls.slice(0, 2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return String(edge?.id || "")
|
|||
|
|
.trim()
|
|||
|
|
.toLowerCase()
|
|||
|
|
.split("-")
|
|||
|
|
.map((wallId) => wallId.trim())
|
|||
|
|
.filter(Boolean)
|
|||
|
|
.slice(0, 2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function edgeLabel(edge) {
|
|||
|
|
const explicitName = String(edge?.name || "").trim();
|
|||
|
|
if (explicitName) {
|
|||
|
|
return explicitName;
|
|||
|
|
}
|
|||
|
|
return edgeWalls(edge)
|
|||
|
|
.map((part) => cap(part))
|
|||
|
|
.join(" ");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function resolveCubeDirectionLabel(wallId, edge) {
|
|||
|
|
const normalizedWallId = String(wallId || "").trim().toLowerCase();
|
|||
|
|
const edgeId = String(edge?.id || "").trim().toLowerCase();
|
|||
|
|
if (!normalizedWallId || !edgeId) {
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const cubeUi = window.CubeSectionUi;
|
|||
|
|
if (cubeUi && typeof cubeUi.getEdgeDirectionLabelForWall === "function") {
|
|||
|
|
const directionLabel = String(cubeUi.getEdgeDirectionLabelForWall(normalizedWallId, edgeId) || "").trim();
|
|||
|
|
if (directionLabel) {
|
|||
|
|
return directionLabel;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return edgeLabel(edge);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const wallById = new Map(
|
|||
|
|
walls.map((wall) => [String(wall?.id || "").trim().toLowerCase(), wall])
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const pathByLetterId = new Map(
|
|||
|
|
paths
|
|||
|
|
.map((path) => [normalizeLetterId(path?.hebrewLetter?.transliteration), path])
|
|||
|
|
.filter(([letterId]) => Boolean(letterId))
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
edges.forEach((edge) => {
|
|||
|
|
const letterId = normalizeLetterId(edge?.hebrewLetterId || edge?.associations?.hebrewLetterId);
|
|||
|
|
const path = pathByLetterId.get(letterId) || null;
|
|||
|
|
const signId = path?.astrology?.type === "zodiac"
|
|||
|
|
? String(path?.astrology?.name || "").trim().toLowerCase()
|
|||
|
|
: "";
|
|||
|
|
|
|||
|
|
if (!signId || placements.has(signId)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const wallsForEdge = edgeWalls(edge);
|
|||
|
|
const primaryWallId = wallsForEdge[0] || "";
|
|||
|
|
const primaryWall = wallById.get(primaryWallId);
|
|||
|
|
|
|||
|
|
placements.set(signId, {
|
|||
|
|
wallId: primaryWallId,
|
|||
|
|
edgeId: String(edge?.id || "").trim().toLowerCase(),
|
|||
|
|
wallName: primaryWall?.name || cap(primaryWallId || "wall"),
|
|||
|
|
edgeName: resolveCubeDirectionLabel(primaryWallId, edge)
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return placements;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function cubePlacementLabel(placement) {
|
|||
|
|
const wallName = placement?.wallName || "Wall";
|
|||
|
|
const edgeName = placement?.edgeName || "Direction";
|
|||
|
|
return `Cube: ${wallName} Wall - ${edgeName}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── List ──────────────────────────────────────────────────────────────
|
|||
|
|
function applyFilter() {
|
|||
|
|
const q = norm(state.searchQuery);
|
|||
|
|
state.filteredEntries = q
|
|||
|
|
? state.entries.filter((s) => buildSearchText(s).includes(q))
|
|||
|
|
: [...state.entries];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderList(els) {
|
|||
|
|
if (!els.listEl) return;
|
|||
|
|
els.listEl.innerHTML = "";
|
|||
|
|
state.filteredEntries.forEach((sign) => {
|
|||
|
|
const active = sign.id === state.selectedId;
|
|||
|
|
const el = document.createElement("div");
|
|||
|
|
el.className = "planet-list-item" + (active ? " is-selected" : "");
|
|||
|
|
el.setAttribute("role", "option");
|
|||
|
|
el.setAttribute("aria-selected", active ? "true" : "false");
|
|||
|
|
el.dataset.id = sign.id;
|
|||
|
|
|
|||
|
|
const elemStyle = ELEMENT_STYLE[sign.elementId] || {};
|
|||
|
|
el.innerHTML = `
|
|||
|
|
<div class="zod-list-row">
|
|||
|
|
<span class="zod-list-symbol">${sign.symbol || "?"}</span>
|
|||
|
|
<span class="planet-list-name">${sign.name?.en || sign.id}</span>
|
|||
|
|
<span class="zod-list-elem ${elemStyle.badge || ""}">${elemStyle.emoji || ""}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="planet-list-meta">${cap(sign.elementId)} · ${cap(sign.quadruplicity)} · ${cap(sign.planetId)}</div>
|
|||
|
|
`;
|
|||
|
|
el.addEventListener("click", () => { selectById(sign.id, els); });
|
|||
|
|
els.listEl.appendChild(el);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (els.countEl) {
|
|||
|
|
els.countEl.textContent = state.searchQuery
|
|||
|
|
? `${state.filteredEntries.length} of ${state.entries.length} signs`
|
|||
|
|
: `${state.entries.length} signs`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Detail ────────────────────────────────────────────────────────────
|
|||
|
|
function renderDetail(sign, els) {
|
|||
|
|
if (!els.detailNameEl) return;
|
|||
|
|
|
|||
|
|
const elemStyle = ELEMENT_STYLE[sign.elementId] || {};
|
|||
|
|
const polarity = ["fire", "air"].includes(sign.elementId) ? "Masculine / Positive" : "Feminine / Negative";
|
|||
|
|
const kabPath = state.kabPaths.find(
|
|||
|
|
(p) => p.astrology?.type === "zodiac" &&
|
|||
|
|
p.astrology?.name?.toLowerCase() === sign.id
|
|||
|
|
);
|
|||
|
|
const decans = state.decansBySign[sign.id] || [];
|
|||
|
|
const monthRefs = state.monthRefsBySignId.get(String(sign.id || "").toLowerCase()) || [];
|
|||
|
|
const cubePlacement = state.cubePlacementBySignId.get(String(sign.id || "").toLowerCase()) || null;
|
|||
|
|
|
|||
|
|
// Heading
|
|||
|
|
els.detailNameEl.textContent = sign.symbol || sign.id;
|
|||
|
|
els.detailSubEl.textContent = `${sign.name?.en || ""} — ${sign.meaning?.en || ""}`;
|
|||
|
|
|
|||
|
|
const sections = [];
|
|||
|
|
|
|||
|
|
// ── Sign Details ──────────────────────────────────────────────────
|
|||
|
|
const elemBadge = `<span class="zod-badge ${elemStyle.badge || ""}">${elemStyle.emoji || ""} ${cap(sign.elementId)}</span>`;
|
|||
|
|
const quadBadge = `<span class="zod-badge zod-badge--quad">${cap(sign.quadruplicity)}</span>`;
|
|||
|
|
sections.push(`<div class="planet-meta-card">
|
|||
|
|
<strong>Sign Details</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<dl class="alpha-dl">
|
|||
|
|
<dt>Symbol</dt><dd>${sign.symbol || "—"}</dd>
|
|||
|
|
<dt>Meaning</dt><dd>${sign.meaning?.en || "—"}</dd>
|
|||
|
|
<dt>Element</dt><dd>${elemBadge}</dd>
|
|||
|
|
<dt>Modality</dt><dd>${quadBadge}</dd>
|
|||
|
|
<dt>Polarity</dt><dd>${polarity}</dd>
|
|||
|
|
<dt>Dates</dt><dd>${formatDateRange(sign.rulesFrom)}</dd>
|
|||
|
|
<dt>Position</dt><dd>#${sign.no} of 12</dd>
|
|||
|
|
</dl>
|
|||
|
|
</div>
|
|||
|
|
</div>`);
|
|||
|
|
|
|||
|
|
// ── Ruling Planet ─────────────────────────────────────────────────
|
|||
|
|
const planetSym = PLANET_SYMBOLS[sign.planetId] || "";
|
|||
|
|
sections.push(`<div class="planet-meta-card">
|
|||
|
|
<strong>Ruling Planet</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<p style="font-size:22px;margin:0 0 6px">${planetSym} ${cap(sign.planetId)}</p>
|
|||
|
|
<button class="alpha-nav-btn" data-nav="planet" data-planet-id="${sign.planetId}">
|
|||
|
|
View ${cap(sign.planetId)} ↗
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>`);
|
|||
|
|
|
|||
|
|
if (cubePlacement) {
|
|||
|
|
sections.push(`<div class="planet-meta-card">
|
|||
|
|
<strong>Cube of Space</strong>
|
|||
|
|
<div class="planet-text">This sign appears in Cube edge correspondences.</div>
|
|||
|
|
<div class="alpha-nav-btns">
|
|||
|
|
<button class="alpha-nav-btn" data-nav="cube-sign" data-sign-id="${sign.id}" data-wall-id="${cubePlacement.wallId}" data-edge-id="${cubePlacement.edgeId}">
|
|||
|
|
${cubePlacementLabel(cubePlacement)} ↗
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Kabbalah Path + Trump ─────────────────────────────────────────
|
|||
|
|
if (kabPath) {
|
|||
|
|
const hl = kabPath.hebrewLetter || {};
|
|||
|
|
sections.push(`<div class="planet-meta-card">
|
|||
|
|
<strong>Kabbalah & Major Arcana</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
|
|||
|
|
<span class="zod-hebrew-glyph">${hl.char || ""}</span>
|
|||
|
|
<div>
|
|||
|
|
<div style="font-weight:600">${hl.transliteration || ""} (${hl.meaning || ""})</div>
|
|||
|
|
<div class="planet-list-meta">${cap(hl.letterType || "")} letter · Path ${kabPath.pathNumber}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<dl class="alpha-dl" style="margin-bottom:8px">
|
|||
|
|
<dt>Trump Card</dt><dd>${kabPath.tarot?.card || "—"}</dd>
|
|||
|
|
<dt>Intelligence</dt><dd>${kabPath.intelligence || "—"}</dd>
|
|||
|
|
</dl>
|
|||
|
|
<div class="alpha-nav-btns">
|
|||
|
|
<button class="alpha-nav-btn" data-nav="kab-path" data-path-number="${kabPath.pathNumber}">
|
|||
|
|
Kabbalah Path ${kabPath.pathNumber} ↗
|
|||
|
|
</button>
|
|||
|
|
<button class="alpha-nav-btn" data-nav="trump" data-trump-number="${kabPath.tarot?.trumpNumber}">
|
|||
|
|
${kabPath.tarot?.card || "Tarot Card"} ↗
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Decans & Minor Arcana ─────────────────────────────────────────
|
|||
|
|
if (decans.length) {
|
|||
|
|
const decanRows = decans.map((d) => {
|
|||
|
|
const ord = ["1st","2nd","3rd"][d.index - 1] || d.index;
|
|||
|
|
const sym = PLANET_SYMBOLS[d.rulerPlanetId] || "";
|
|||
|
|
return `<div class="zod-decan-row">
|
|||
|
|
<span class="zod-decan-ord">${ord}</span>
|
|||
|
|
<span class="zod-decan-planet">${sym} ${cap(d.rulerPlanetId)}</span>
|
|||
|
|
<button class="zod-decan-card-btn" data-nav="tarot-card" data-card-name="${d.tarotMinorArcana}">
|
|||
|
|
${d.tarotMinorArcana} ↗
|
|||
|
|
</button>
|
|||
|
|
</div>`;
|
|||
|
|
}).join("");
|
|||
|
|
sections.push(`<div class="planet-meta-card">
|
|||
|
|
<strong>Decans & Minor Arcana</strong>
|
|||
|
|
<div class="planet-text">${decanRows}</div>
|
|||
|
|
</div>`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (monthRefs.length) {
|
|||
|
|
const monthButtons = monthRefs.map((month) =>
|
|||
|
|
`<button class="alpha-nav-btn" data-nav="calendar-month" data-month-id="${month.id}">${month.name} ↗</button>`
|
|||
|
|
).join("");
|
|||
|
|
|
|||
|
|
sections.push(`<div class="planet-meta-card">
|
|||
|
|
<strong>Calendar Months</strong>
|
|||
|
|
<div class="planet-text">Month correspondences linked to ${sign.name?.en || sign.id}.</div>
|
|||
|
|
<div class="alpha-nav-btns">${monthButtons}</div>
|
|||
|
|
</div>`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Kabbalah extras ───────────────────────────────────────────────
|
|||
|
|
if (sign.tribeOfIsraelId || sign.tetragrammatonPermutation) {
|
|||
|
|
sections.push(`<div class="planet-meta-card">
|
|||
|
|
<strong>Kabbalah Correspondences</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<dl class="alpha-dl">
|
|||
|
|
${sign.tribeOfIsraelId ? `<dt>Tribe of Israel</dt><dd>${cap(sign.tribeOfIsraelId)}</dd>` : ""}
|
|||
|
|
${sign.tetragrammatonPermutation ? `<dt>Tetragrammaton</dt><dd class="zod-tetra">${sign.tetragrammatonPermutation}</dd>` : ""}
|
|||
|
|
</dl>
|
|||
|
|
</div>
|
|||
|
|
</div>`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
els.detailBodyEl.innerHTML = `<div class="planet-meta-grid">${sections.join("")}</div>`;
|
|||
|
|
|
|||
|
|
// Attach button listeners
|
|||
|
|
els.detailBodyEl.querySelectorAll("[data-nav]").forEach((btn) => {
|
|||
|
|
btn.addEventListener("click", () => {
|
|||
|
|
const nav = btn.dataset.nav;
|
|||
|
|
if (nav === "planet") {
|
|||
|
|
document.dispatchEvent(new CustomEvent("nav:planet", {
|
|||
|
|
detail: { planetId: btn.dataset.planetId }
|
|||
|
|
}));
|
|||
|
|
} else if (nav === "kab-path") {
|
|||
|
|
document.dispatchEvent(new CustomEvent("tarot:view-kab-path", {
|
|||
|
|
detail: { pathNumber: Number(btn.dataset.pathNumber) }
|
|||
|
|
}));
|
|||
|
|
} else if (nav === "trump") {
|
|||
|
|
document.dispatchEvent(new CustomEvent("kab:view-trump", {
|
|||
|
|
detail: { trumpNumber: Number(btn.dataset.trumpNumber) }
|
|||
|
|
}));
|
|||
|
|
} else if (nav === "tarot-card") {
|
|||
|
|
document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
|
|||
|
|
detail: { cardName: btn.dataset.cardName }
|
|||
|
|
}));
|
|||
|
|
} else if (nav === "calendar-month") {
|
|||
|
|
document.dispatchEvent(new CustomEvent("nav:calendar-month", {
|
|||
|
|
detail: { monthId: btn.dataset.monthId }
|
|||
|
|
}));
|
|||
|
|
} else if (nav === "cube-sign") {
|
|||
|
|
document.dispatchEvent(new CustomEvent("nav:cube", {
|
|||
|
|
detail: {
|
|||
|
|
signId: btn.dataset.signId,
|
|||
|
|
wallId: btn.dataset.wallId,
|
|||
|
|
edgeId: btn.dataset.edgeId
|
|||
|
|
}
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function resetDetail(els) {
|
|||
|
|
if (els.detailNameEl) els.detailNameEl.textContent = "--";
|
|||
|
|
if (els.detailSubEl) els.detailSubEl.textContent = "Select a sign to explore";
|
|||
|
|
if (els.detailBodyEl) els.detailBodyEl.innerHTML = "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Selection ─────────────────────────────────────────────────────────
|
|||
|
|
function selectById(id, els) {
|
|||
|
|
const sign = state.entries.find((s) => s.id === id);
|
|||
|
|
if (!sign) return;
|
|||
|
|
state.selectedId = id;
|
|||
|
|
renderList(els);
|
|||
|
|
renderDetail(sign, els);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Public select (for incoming navigation) ───────────────────────────
|
|||
|
|
function selectBySignId(signId) {
|
|||
|
|
const els = getElements();
|
|||
|
|
if (!state.initialized) return;
|
|||
|
|
const sign = state.entries.find((s) => s.id === signId);
|
|||
|
|
if (sign) selectById(signId, els);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Init ───────────────────────────────────────────────────────────────
|
|||
|
|
function ensureZodiacSection(referenceData, magickDataset) {
|
|||
|
|
state.monthRefsBySignId = buildMonthReferencesBySign(referenceData);
|
|||
|
|
state.cubePlacementBySignId = buildCubeSignPlacements(magickDataset);
|
|||
|
|
|
|||
|
|
if (state.initialized) {
|
|||
|
|
const els = getElements();
|
|||
|
|
const current = state.entries.find((entry) => entry.id === state.selectedId);
|
|||
|
|
if (current) {
|
|||
|
|
renderDetail(current, els);
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
state.initialized = true;
|
|||
|
|
|
|||
|
|
const zodiacObj = magickDataset?.grouped?.astrology?.zodiac || {};
|
|||
|
|
state.entries = Object.values(zodiacObj).sort((a, b) => (a.no || 0) - (b.no || 0));
|
|||
|
|
|
|||
|
|
const kabTree = magickDataset?.grouped?.kabbalah?.["kabbalah-tree"];
|
|||
|
|
state.kabPaths = Array.isArray(kabTree?.paths) ? kabTree.paths : [];
|
|||
|
|
|
|||
|
|
state.decansBySign = referenceData?.decansBySign || {};
|
|||
|
|
|
|||
|
|
const els = getElements();
|
|||
|
|
applyFilter();
|
|||
|
|
renderList(els);
|
|||
|
|
|
|||
|
|
if (state.entries.length > 0) {
|
|||
|
|
selectById(state.entries[0].id, els);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Search
|
|||
|
|
if (els.searchEl) {
|
|||
|
|
els.searchEl.addEventListener("input", () => {
|
|||
|
|
state.searchQuery = els.searchEl.value;
|
|||
|
|
if (els.searchClearEl) els.searchClearEl.disabled = !state.searchQuery;
|
|||
|
|
applyFilter();
|
|||
|
|
renderList(els);
|
|||
|
|
if (!state.filteredEntries.some((s) => s.id === state.selectedId)) {
|
|||
|
|
if (state.filteredEntries.length > 0) {
|
|||
|
|
selectById(state.filteredEntries[0].id, els);
|
|||
|
|
} else {
|
|||
|
|
state.selectedId = null;
|
|||
|
|
resetDetail(els);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (els.searchClearEl) {
|
|||
|
|
els.searchClearEl.addEventListener("click", () => {
|
|||
|
|
state.searchQuery = "";
|
|||
|
|
if (els.searchEl) els.searchEl.value = "";
|
|||
|
|
els.searchClearEl.disabled = true;
|
|||
|
|
applyFilter();
|
|||
|
|
renderList(els);
|
|||
|
|
if (state.entries.length > 0) selectById(state.entries[0].id, els);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
window.ZodiacSectionUi = {
|
|||
|
|
ensureZodiacSection,
|
|||
|
|
selectBySignId
|
|||
|
|
};
|
|||
|
|
})();
|