899 lines
29 KiB
JavaScript
899 lines
29 KiB
JavaScript
(function () {
|
||
const { getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
||
|
||
const state = {
|
||
initialized: false,
|
||
entries: [],
|
||
filteredEntries: [],
|
||
searchQuery: "",
|
||
selectedId: "",
|
||
kabbalahTargetsByPlanetId: {},
|
||
monthRefsByPlanetId: new Map(),
|
||
cubePlacementsByPlanetId: new Map()
|
||
};
|
||
|
||
function normalizePlanetToken(value) {
|
||
return String(value || "")
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace(/[^a-z]/g, "");
|
||
}
|
||
|
||
function toPlanetId(value) {
|
||
const token = normalizePlanetToken(value);
|
||
if (!token) return null;
|
||
|
||
if (token === "sun") return "sol";
|
||
if (token === "moon") return "luna";
|
||
if (["saturn", "jupiter", "mars", "sol", "venus", "mercury", "luna"].includes(token)) {
|
||
return token;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function appendKabbalahTarget(map, planetId, target) {
|
||
if (!planetId || !target) return;
|
||
if (!map[planetId]) map[planetId] = [];
|
||
const key = `${target.kind}:${target.number}`;
|
||
if (map[planetId].some((entry) => `${entry.kind}:${entry.number}` === key)) return;
|
||
map[planetId].push(target);
|
||
}
|
||
|
||
function buildKabbalahTargetsByPlanet(magickDataset) {
|
||
const tree = magickDataset?.grouped?.kabbalah?.["kabbalah-tree"];
|
||
const map = {};
|
||
if (!tree) return map;
|
||
|
||
(tree.sephiroth || []).forEach((seph) => {
|
||
const planetId = toPlanetId(seph?.planet);
|
||
if (!planetId) return;
|
||
appendKabbalahTarget(map, planetId, {
|
||
kind: "sephirah",
|
||
number: seph.number,
|
||
label: `Sephirah ${seph.number} · ${seph.name}`
|
||
});
|
||
});
|
||
|
||
(tree.paths || []).forEach((path) => {
|
||
if (path?.astrology?.type !== "planet") return;
|
||
const planetId = toPlanetId(path?.astrology?.name);
|
||
if (!planetId) return;
|
||
appendKabbalahTarget(map, planetId, {
|
||
kind: "path",
|
||
number: path.pathNumber,
|
||
label: `Path ${path.pathNumber} · ${path?.tarot?.card || path?.hebrewLetter?.transliteration || ""}`.trim()
|
||
});
|
||
});
|
||
|
||
return map;
|
||
}
|
||
|
||
function buildMonthReferencesByPlanet(referenceData) {
|
||
const map = new Map();
|
||
const months = Array.isArray(referenceData?.calendarMonths) ? referenceData.calendarMonths : [];
|
||
const holidays = Array.isArray(referenceData?.celestialHolidays) ? referenceData.celestialHolidays : [];
|
||
const monthById = new Map(months.map((month) => [month.id, month]));
|
||
|
||
function parseMonthDayToken(value) {
|
||
const text = String(value || "").trim();
|
||
const match = text.match(/^(\d{1,2})-(\d{1,2})$/);
|
||
if (!match) {
|
||
return null;
|
||
}
|
||
|
||
const monthNo = Number(match[1]);
|
||
const dayNo = Number(match[2]);
|
||
if (!Number.isInteger(monthNo) || !Number.isInteger(dayNo) || monthNo < 1 || monthNo > 12 || dayNo < 1 || dayNo > 31) {
|
||
return null;
|
||
}
|
||
|
||
return { month: monthNo, day: dayNo };
|
||
}
|
||
|
||
function parseMonthDayTokensFromText(value) {
|
||
const text = String(value || "");
|
||
const matches = [...text.matchAll(/(\d{1,2})-(\d{1,2})/g)];
|
||
return matches
|
||
.map((match) => ({ month: Number(match[1]), day: Number(match[2]) }))
|
||
.filter((token) => Number.isInteger(token.month) && Number.isInteger(token.day) && token.month >= 1 && token.month <= 12 && token.day >= 1 && token.day <= 31);
|
||
}
|
||
|
||
function toDateToken(token, year) {
|
||
if (!token) {
|
||
return null;
|
||
}
|
||
return new Date(year, token.month - 1, token.day, 12, 0, 0, 0);
|
||
}
|
||
|
||
function splitMonthDayRangeByMonth(startToken, endToken) {
|
||
const startDate = toDateToken(startToken, 2025);
|
||
const endBase = toDateToken(endToken, 2025);
|
||
if (!startDate || !endBase) {
|
||
return [];
|
||
}
|
||
|
||
const wrapsYear = endBase.getTime() < startDate.getTime();
|
||
const endDate = wrapsYear ? toDateToken(endToken, 2026) : endBase;
|
||
if (!endDate) {
|
||
return [];
|
||
}
|
||
|
||
const segments = [];
|
||
let cursor = new Date(startDate);
|
||
while (cursor.getTime() <= endDate.getTime()) {
|
||
const monthEnd = new Date(cursor.getFullYear(), cursor.getMonth() + 1, 0, 12, 0, 0, 0);
|
||
const segmentEnd = monthEnd.getTime() < endDate.getTime() ? monthEnd : endDate;
|
||
|
||
segments.push({
|
||
monthNo: cursor.getMonth() + 1,
|
||
startDay: cursor.getDate(),
|
||
endDay: segmentEnd.getDate()
|
||
});
|
||
|
||
cursor = new Date(segmentEnd.getFullYear(), segmentEnd.getMonth(), segmentEnd.getDate() + 1, 12, 0, 0, 0);
|
||
}
|
||
|
||
return segments;
|
||
}
|
||
|
||
function tokenToString(monthNo, dayNo) {
|
||
return `${String(monthNo).padStart(2, "0")}-${String(dayNo).padStart(2, "0")}`;
|
||
}
|
||
|
||
function formatRangeLabel(monthName, startDay, endDay) {
|
||
if (!Number.isFinite(startDay) || !Number.isFinite(endDay)) {
|
||
return monthName;
|
||
}
|
||
if (startDay === endDay) {
|
||
return `${monthName} ${startDay}`;
|
||
}
|
||
return `${monthName} ${startDay}-${endDay}`;
|
||
}
|
||
|
||
function resolveRangeForMonth(month, options = {}) {
|
||
const monthOrder = Number(month?.order);
|
||
const monthStart = parseMonthDayToken(month?.start);
|
||
const monthEnd = parseMonthDayToken(month?.end);
|
||
if (!Number.isFinite(monthOrder) || !monthStart || !monthEnd) {
|
||
return {
|
||
startToken: String(month?.start || "").trim() || null,
|
||
endToken: String(month?.end || "").trim() || null,
|
||
label: month?.name || month?.id || "",
|
||
isFullMonth: true
|
||
};
|
||
}
|
||
|
||
let startToken = parseMonthDayToken(options.startToken);
|
||
let endToken = parseMonthDayToken(options.endToken);
|
||
|
||
if (!startToken || !endToken) {
|
||
const tokens = parseMonthDayTokensFromText(options.rawDateText);
|
||
if (tokens.length >= 2) {
|
||
startToken = tokens[0];
|
||
endToken = tokens[1];
|
||
} else if (tokens.length === 1) {
|
||
startToken = tokens[0];
|
||
endToken = tokens[0];
|
||
}
|
||
}
|
||
|
||
if (!startToken || !endToken) {
|
||
startToken = monthStart;
|
||
endToken = monthEnd;
|
||
}
|
||
|
||
const segments = splitMonthDayRangeByMonth(startToken, endToken);
|
||
const segment = segments.find((entry) => entry.monthNo === monthOrder) || null;
|
||
|
||
const useStart = segment ? { month: monthOrder, day: segment.startDay } : startToken;
|
||
const useEnd = segment ? { month: monthOrder, day: segment.endDay } : endToken;
|
||
const startText = tokenToString(useStart.month, useStart.day);
|
||
const endText = tokenToString(useEnd.month, useEnd.day);
|
||
const isFullMonth = startText === month.start && endText === month.end;
|
||
|
||
return {
|
||
startToken: startText,
|
||
endToken: endText,
|
||
label: isFullMonth
|
||
? (month.name || month.id)
|
||
: formatRangeLabel(month.name || month.id, useStart.day, useEnd.day),
|
||
isFullMonth
|
||
};
|
||
}
|
||
|
||
function pushRef(planetToken, month, options = {}) {
|
||
const planetId = toPlanetId(planetToken) || normalizePlanetToken(planetToken);
|
||
if (!planetId || !month?.id) {
|
||
return;
|
||
}
|
||
|
||
if (!map.has(planetId)) {
|
||
map.set(planetId, []);
|
||
}
|
||
|
||
const rows = map.get(planetId);
|
||
const range = resolveRangeForMonth(month, options);
|
||
const key = `${month.id}|${range.startToken || ""}|${range.endToken || ""}`;
|
||
if (rows.some((entry) => entry.key === key)) {
|
||
return;
|
||
}
|
||
|
||
rows.push({
|
||
id: month.id,
|
||
name: month.name || month.id,
|
||
order: Number.isFinite(Number(month.order)) ? Number(month.order) : 999,
|
||
label: range.label,
|
||
startToken: range.startToken,
|
||
endToken: range.endToken,
|
||
isFullMonth: range.isFullMonth,
|
||
key
|
||
});
|
||
}
|
||
|
||
months.forEach((month) => {
|
||
pushRef(month?.associations?.planetId, month);
|
||
const events = Array.isArray(month?.events) ? month.events : [];
|
||
events.forEach((event) => {
|
||
pushRef(event?.associations?.planetId, month, {
|
||
rawDateText: event?.dateRange || event?.date || ""
|
||
});
|
||
});
|
||
});
|
||
|
||
holidays.forEach((holiday) => {
|
||
const month = monthById.get(holiday?.monthId);
|
||
if (!month) {
|
||
return;
|
||
}
|
||
pushRef(holiday?.associations?.planetId, month, {
|
||
rawDateText: holiday?.dateRange || holiday?.date || ""
|
||
});
|
||
});
|
||
|
||
map.forEach((rows, key) => {
|
||
const preciseMonthIds = new Set(
|
||
rows
|
||
.filter((entry) => !entry.isFullMonth)
|
||
.map((entry) => entry.id)
|
||
);
|
||
|
||
const filtered = rows.filter((entry) => {
|
||
if (!entry.isFullMonth) {
|
||
return true;
|
||
}
|
||
return !preciseMonthIds.has(entry.id);
|
||
});
|
||
|
||
filtered.sort((left, right) => {
|
||
if (left.order !== right.order) {
|
||
return left.order - right.order;
|
||
}
|
||
|
||
const startLeft = parseMonthDayToken(left.startToken);
|
||
const startRight = parseMonthDayToken(right.startToken);
|
||
const dayLeft = startLeft ? startLeft.day : 999;
|
||
const dayRight = startRight ? startRight.day : 999;
|
||
if (dayLeft !== dayRight) {
|
||
return dayLeft - dayRight;
|
||
}
|
||
|
||
return String(left.label || left.name || "").localeCompare(String(right.label || right.name || ""));
|
||
});
|
||
|
||
map.set(key, filtered);
|
||
});
|
||
|
||
return map;
|
||
}
|
||
|
||
function buildCubePlacementsByPlanet(magickDataset) {
|
||
const map = new Map();
|
||
const cube = magickDataset?.grouped?.kabbalah?.cube || {};
|
||
const walls = Array.isArray(cube?.walls)
|
||
? cube.walls
|
||
: [];
|
||
const edges = Array.isArray(cube?.edges)
|
||
? cube.edges
|
||
: [];
|
||
|
||
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) => part.charAt(0).toUpperCase() + part.slice(1))
|
||
.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 firstEdgeByWallId = new Map();
|
||
edges.forEach((edge) => {
|
||
edgeWalls(edge).forEach((wallId) => {
|
||
if (!firstEdgeByWallId.has(wallId)) {
|
||
firstEdgeByWallId.set(wallId, edge);
|
||
}
|
||
});
|
||
});
|
||
|
||
function pushPlacement(planetId, placement) {
|
||
if (!planetId || !placement?.wallId || !placement?.edgeId) {
|
||
return;
|
||
}
|
||
|
||
if (!map.has(planetId)) {
|
||
map.set(planetId, []);
|
||
}
|
||
|
||
const rows = map.get(planetId);
|
||
const key = `${placement.wallId}:${placement.edgeId}`;
|
||
if (rows.some((row) => `${row.wallId}:${row.edgeId}` === key)) {
|
||
return;
|
||
}
|
||
|
||
rows.push(placement);
|
||
}
|
||
|
||
walls.forEach((wall) => {
|
||
const planetId = toPlanetId(wall?.associations?.planetId || wall?.planet);
|
||
if (!planetId) {
|
||
return;
|
||
}
|
||
|
||
const wallId = String(wall?.id || "").trim().toLowerCase();
|
||
const edge = firstEdgeByWallId.get(wallId) || null;
|
||
|
||
pushPlacement(planetId, {
|
||
wallId,
|
||
edgeId: String(edge?.id || "").trim().toLowerCase(),
|
||
label: `Cube: ${wall?.name || "Wall"} Wall - ${resolveCubeDirectionLabel(wallId, edge) || "Direction"}`
|
||
});
|
||
});
|
||
|
||
return map;
|
||
}
|
||
|
||
function getElements() {
|
||
return {
|
||
planetCardListEl: document.getElementById("planet-card-list"),
|
||
planetSearchInputEl: document.getElementById("planet-search-input"),
|
||
planetSearchClearEl: document.getElementById("planet-search-clear"),
|
||
planetCountEl: document.getElementById("planet-card-count"),
|
||
planetDetailNameEl: document.getElementById("planet-detail-name"),
|
||
planetDetailTypeEl: document.getElementById("planet-detail-type"),
|
||
planetDetailSummaryEl: document.getElementById("planet-detail-summary"),
|
||
planetDetailFactsEl: document.getElementById("planet-detail-facts"),
|
||
planetDetailAtmosphereEl: document.getElementById("planet-detail-atmosphere"),
|
||
planetDetailNotableEl: document.getElementById("planet-detail-notable"),
|
||
planetDetailCorrespondenceEl: document.getElementById("planet-detail-correspondence")
|
||
};
|
||
}
|
||
|
||
function normalizeSearchValue(value) {
|
||
return String(value || "").trim().toLowerCase();
|
||
}
|
||
|
||
function buildPlanetSearchText(entry) {
|
||
const correspondence = entry?.correspondence || {};
|
||
const factValues = buildFactRows(entry).map(([, value]) => String(value || ""));
|
||
const tarotAliases = correspondence?.tarot?.majorArcana && typeof getTarotCardSearchAliases === "function"
|
||
? getTarotCardSearchAliases(correspondence.tarot.majorArcana)
|
||
: [];
|
||
const rawNumbers = [
|
||
entry?.meanDistanceFromSun?.kmMillions,
|
||
entry?.meanDistanceFromSun?.au,
|
||
entry?.orbitalPeriod?.days,
|
||
entry?.orbitalPeriod?.years,
|
||
entry?.rotationPeriodHours,
|
||
entry?.radiusKm,
|
||
entry?.diameterKm,
|
||
entry?.massKg,
|
||
entry?.gravityMs2,
|
||
entry?.escapeVelocityKms,
|
||
entry?.axialTiltDeg,
|
||
entry?.averageTempC,
|
||
entry?.moons
|
||
]
|
||
.filter((value) => Number.isFinite(value))
|
||
.map((value) => String(value));
|
||
|
||
const parts = [
|
||
entry?.name,
|
||
entry?.symbol,
|
||
entry?.classification,
|
||
entry?.summary,
|
||
entry?.atmosphere,
|
||
...(Array.isArray(entry?.notableFacts) ? entry.notableFacts : []),
|
||
...factValues,
|
||
...rawNumbers,
|
||
correspondence?.name,
|
||
correspondence?.symbol,
|
||
correspondence?.weekday,
|
||
correspondence?.tarot?.majorArcana,
|
||
...tarotAliases
|
||
];
|
||
|
||
return normalizeSearchValue(parts.filter(Boolean).join(" "));
|
||
}
|
||
|
||
function applySearchFilter(elements) {
|
||
const query = normalizeSearchValue(state.searchQuery);
|
||
state.filteredEntries = query
|
||
? state.entries.filter((entry) => buildPlanetSearchText(entry).includes(query))
|
||
: [...state.entries];
|
||
|
||
if (elements?.planetSearchClearEl) {
|
||
elements.planetSearchClearEl.disabled = !query;
|
||
}
|
||
|
||
renderList(elements);
|
||
|
||
if (!state.filteredEntries.some((entry) => entry.id === state.selectedId)) {
|
||
if (state.filteredEntries.length > 0) {
|
||
selectById(state.filteredEntries[0].id, elements);
|
||
}
|
||
return;
|
||
}
|
||
|
||
updateSelection(elements);
|
||
}
|
||
|
||
function clearChildren(element) {
|
||
if (element) {
|
||
element.replaceChildren();
|
||
}
|
||
}
|
||
|
||
function toNumber(value) {
|
||
return Number.isFinite(value) ? value : null;
|
||
}
|
||
|
||
function formatNumber(value, maximumFractionDigits = 2) {
|
||
if (!Number.isFinite(value)) {
|
||
return "--";
|
||
}
|
||
|
||
return new Intl.NumberFormat(undefined, {
|
||
maximumFractionDigits,
|
||
minimumFractionDigits: 0
|
||
}).format(value);
|
||
}
|
||
|
||
function formatSignedHours(value) {
|
||
if (!Number.isFinite(value)) {
|
||
return "--";
|
||
}
|
||
|
||
const absValue = Math.abs(value);
|
||
const formatted = `${formatNumber(absValue, 2)} h`;
|
||
return value < 0 ? `${formatted} (retrograde)` : formatted;
|
||
}
|
||
|
||
function formatMass(value) {
|
||
if (!Number.isFinite(value)) {
|
||
return "--";
|
||
}
|
||
|
||
return value.toExponential(3).replace("e+", " × 10^") + " kg";
|
||
}
|
||
|
||
function buildFactRows(entry) {
|
||
return [
|
||
["Mean distance from Sun", Number.isFinite(entry?.meanDistanceFromSun?.kmMillions) && Number.isFinite(entry?.meanDistanceFromSun?.au)
|
||
? `${formatNumber(entry.meanDistanceFromSun.kmMillions, 1)} million km (${formatNumber(entry.meanDistanceFromSun.au, 3)} AU)`
|
||
: "--"],
|
||
["Orbital period", Number.isFinite(entry?.orbitalPeriod?.days) && Number.isFinite(entry?.orbitalPeriod?.years)
|
||
? `${formatNumber(entry.orbitalPeriod.days, 2)} days (${formatNumber(entry.orbitalPeriod.years, 3)} years)`
|
||
: "--"],
|
||
["Rotation period", formatSignedHours(toNumber(entry?.rotationPeriodHours))],
|
||
["Radius", Number.isFinite(entry?.radiusKm) ? `${formatNumber(entry.radiusKm, 1)} km` : "--"],
|
||
["Diameter", Number.isFinite(entry?.diameterKm) ? `${formatNumber(entry.diameterKm, 1)} km` : "--"],
|
||
["Mass", formatMass(toNumber(entry?.massKg))],
|
||
["Surface gravity", Number.isFinite(entry?.gravityMs2) ? `${formatNumber(entry.gravityMs2, 3)} m/s²` : "--"],
|
||
["Escape velocity", Number.isFinite(entry?.escapeVelocityKms) ? `${formatNumber(entry.escapeVelocityKms, 2)} km/s` : "--"],
|
||
["Axial tilt", Number.isFinite(entry?.axialTiltDeg) ? `${formatNumber(entry.axialTiltDeg, 2)}°` : "--"],
|
||
["Average temperature", Number.isFinite(entry?.averageTempC) ? `${formatNumber(entry.averageTempC, 0)} °C` : "--"],
|
||
["Moons", Number.isFinite(entry?.moons) ? formatNumber(entry.moons, 0) : "--"]
|
||
];
|
||
}
|
||
|
||
function getDisplayTarotName(cardName, trumpNumber) {
|
||
if (!cardName) {
|
||
return "";
|
||
}
|
||
|
||
if (typeof getTarotCardDisplayName !== "function") {
|
||
return cardName;
|
||
}
|
||
|
||
if (Number.isFinite(Number(trumpNumber))) {
|
||
return getTarotCardDisplayName(cardName, { trumpNumber: Number(trumpNumber) }) || cardName;
|
||
}
|
||
|
||
return getTarotCardDisplayName(cardName) || cardName;
|
||
}
|
||
|
||
function renderCorrespondence(entry, containerEl) {
|
||
if (!containerEl) return;
|
||
containerEl.innerHTML = "";
|
||
|
||
const correspondence = entry?.correspondence;
|
||
|
||
if (!correspondence || typeof correspondence !== "object") {
|
||
const fallback = document.createElement("span");
|
||
fallback.className = "planet-text";
|
||
fallback.textContent = "No tarot/day correspondence in current local dataset.";
|
||
containerEl.appendChild(fallback);
|
||
}
|
||
|
||
const symbol = correspondence?.symbol || "";
|
||
const weekday = correspondence?.weekday || "";
|
||
const arcana = correspondence?.tarot?.majorArcana || "";
|
||
const trumpNo = correspondence?.tarot?.number;
|
||
const arcanaLabel = getDisplayTarotName(arcana, trumpNo);
|
||
|
||
// Symbol + weekday line
|
||
if (symbol || weekday) {
|
||
const line = document.createElement("span");
|
||
line.className = "planet-text";
|
||
line.textContent = [symbol, weekday].filter(Boolean).join(" \u00b7 ");
|
||
containerEl.appendChild(line);
|
||
}
|
||
|
||
// Tarot card link
|
||
if (arcana) {
|
||
const btn = document.createElement("button");
|
||
btn.type = "button";
|
||
btn.className = "kab-tarot-link";
|
||
btn.style.marginTop = "8px";
|
||
btn.textContent = trumpNo != null ? `${arcanaLabel} \u00b7 Trump ${trumpNo}` : arcanaLabel;
|
||
btn.title = "Open in Tarot section";
|
||
btn.addEventListener("click", () => {
|
||
document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
|
||
detail: { trumpNumber: trumpNo ?? null, cardName: arcana }
|
||
}));
|
||
});
|
||
containerEl.appendChild(btn);
|
||
}
|
||
|
||
const planetId = toPlanetId(correspondence?.id || entry?.id || entry?.name) ||
|
||
normalizePlanetToken(correspondence?.id || entry?.id || entry?.name);
|
||
const kabbalahTargets = state.kabbalahTargetsByPlanetId[planetId] || [];
|
||
if (kabbalahTargets.length) {
|
||
const row = document.createElement("div");
|
||
row.className = "kab-god-links";
|
||
row.style.marginTop = "8px";
|
||
|
||
kabbalahTargets.forEach((target) => {
|
||
const btn = document.createElement("button");
|
||
btn.type = "button";
|
||
btn.className = "kab-god-link";
|
||
btn.textContent = `View ${target.label} ↗`;
|
||
btn.addEventListener("click", () => {
|
||
document.dispatchEvent(new CustomEvent("nav:kabbalah-path", {
|
||
detail: { pathNo: Number(target.number) }
|
||
}));
|
||
});
|
||
row.appendChild(btn);
|
||
});
|
||
|
||
containerEl.appendChild(row);
|
||
}
|
||
|
||
const monthRefs = state.monthRefsByPlanetId.get(planetId) || [];
|
||
if (monthRefs.length) {
|
||
const meta = document.createElement("div");
|
||
meta.className = "kab-god-meta";
|
||
meta.textContent = "Calendar month correspondences";
|
||
containerEl.appendChild(meta);
|
||
|
||
const row = document.createElement("div");
|
||
row.className = "kab-god-links";
|
||
monthRefs.forEach((month) => {
|
||
const btn = document.createElement("button");
|
||
btn.type = "button";
|
||
btn.className = "kab-god-link";
|
||
btn.textContent = `${month.label || month.name} ↗`;
|
||
btn.addEventListener("click", () => {
|
||
document.dispatchEvent(new CustomEvent("nav:calendar-month", {
|
||
detail: { monthId: month.id }
|
||
}));
|
||
});
|
||
row.appendChild(btn);
|
||
});
|
||
containerEl.appendChild(row);
|
||
}
|
||
|
||
const cubePlacements = state.cubePlacementsByPlanetId.get(planetId) || [];
|
||
if (cubePlacements.length) {
|
||
const meta = document.createElement("div");
|
||
meta.className = "kab-god-meta";
|
||
meta.textContent = "Cube placements";
|
||
containerEl.appendChild(meta);
|
||
|
||
const row = document.createElement("div");
|
||
row.className = "kab-god-links";
|
||
cubePlacements.forEach((placement) => {
|
||
const btn = document.createElement("button");
|
||
btn.type = "button";
|
||
btn.className = "kab-god-link";
|
||
btn.textContent = `${placement.label} ↗`;
|
||
btn.addEventListener("click", () => {
|
||
document.dispatchEvent(new CustomEvent("nav:cube", {
|
||
detail: {
|
||
planetId,
|
||
wallId: placement.wallId,
|
||
edgeId: placement.edgeId
|
||
}
|
||
}));
|
||
});
|
||
row.appendChild(btn);
|
||
});
|
||
containerEl.appendChild(row);
|
||
}
|
||
}
|
||
|
||
function renderDetail(entry, elements) {
|
||
if (!entry || !elements) {
|
||
return;
|
||
}
|
||
|
||
if (elements.planetDetailNameEl) {
|
||
const symbol = entry.symbol ? `${entry.symbol} ` : "";
|
||
elements.planetDetailNameEl.textContent = `${symbol}${entry.name || "--"}`;
|
||
}
|
||
|
||
if (elements.planetDetailTypeEl) {
|
||
elements.planetDetailTypeEl.textContent = entry.classification || "--";
|
||
}
|
||
|
||
if (elements.planetDetailSummaryEl) {
|
||
elements.planetDetailSummaryEl.textContent = entry.summary || "--";
|
||
}
|
||
|
||
clearChildren(elements.planetDetailFactsEl);
|
||
buildFactRows(entry).forEach(([label, value]) => {
|
||
const row = document.createElement("div");
|
||
row.className = "planet-fact-row";
|
||
|
||
const labelEl = document.createElement("span");
|
||
labelEl.className = "planet-fact-label";
|
||
labelEl.textContent = label;
|
||
|
||
const valueEl = document.createElement("span");
|
||
valueEl.className = "planet-fact-value";
|
||
valueEl.textContent = value;
|
||
|
||
row.append(labelEl, valueEl);
|
||
elements.planetDetailFactsEl?.appendChild(row);
|
||
});
|
||
|
||
if (elements.planetDetailAtmosphereEl) {
|
||
elements.planetDetailAtmosphereEl.textContent = entry.atmosphere || "--";
|
||
}
|
||
|
||
clearChildren(elements.planetDetailNotableEl);
|
||
const notableFacts = Array.isArray(entry.notableFacts) ? entry.notableFacts : [];
|
||
notableFacts.forEach((fact) => {
|
||
const item = document.createElement("li");
|
||
item.textContent = fact;
|
||
elements.planetDetailNotableEl?.appendChild(item);
|
||
});
|
||
|
||
if (elements.planetDetailCorrespondenceEl) {
|
||
renderCorrespondence(entry, elements.planetDetailCorrespondenceEl);
|
||
}
|
||
}
|
||
|
||
function updateSelection(elements) {
|
||
if (!elements?.planetCardListEl) {
|
||
return;
|
||
}
|
||
|
||
const buttons = elements.planetCardListEl.querySelectorAll(".planet-list-item");
|
||
buttons.forEach((button) => {
|
||
const isSelected = button.dataset.planetId === state.selectedId;
|
||
button.classList.toggle("is-selected", isSelected);
|
||
button.setAttribute("aria-selected", isSelected ? "true" : "false");
|
||
});
|
||
}
|
||
|
||
function selectById(id, elements) {
|
||
const entry = state.entries.find((planet) => planet.id === id);
|
||
if (!entry) {
|
||
return;
|
||
}
|
||
|
||
state.selectedId = entry.id;
|
||
updateSelection(elements);
|
||
renderDetail(entry, elements);
|
||
}
|
||
|
||
function renderList(elements) {
|
||
if (!elements?.planetCardListEl) {
|
||
return;
|
||
}
|
||
|
||
clearChildren(elements.planetCardListEl);
|
||
|
||
state.filteredEntries.forEach((entry) => {
|
||
const button = document.createElement("button");
|
||
button.type = "button";
|
||
button.className = "planet-list-item";
|
||
button.dataset.planetId = entry.id;
|
||
button.setAttribute("role", "option");
|
||
|
||
const nameEl = document.createElement("span");
|
||
nameEl.className = "planet-list-name";
|
||
const symbol = entry.symbol ? `${entry.symbol} ` : "";
|
||
nameEl.textContent = `${symbol}${entry.name || "--"}`;
|
||
|
||
const metaEl = document.createElement("span");
|
||
metaEl.className = "planet-list-meta";
|
||
metaEl.textContent = entry.classification || "--";
|
||
|
||
button.append(nameEl, metaEl);
|
||
elements.planetCardListEl.appendChild(button);
|
||
});
|
||
|
||
if (elements.planetCountEl) {
|
||
elements.planetCountEl.textContent = state.searchQuery
|
||
? `${state.filteredEntries.length} of ${state.entries.length} bodies`
|
||
: `${state.entries.length} bodies`;
|
||
}
|
||
}
|
||
|
||
function ensurePlanetSection(referenceData, magickDataset = null) {
|
||
if (state.initialized) {
|
||
return;
|
||
}
|
||
|
||
const elements = getElements();
|
||
if (!elements.planetCardListEl || !elements.planetDetailNameEl) {
|
||
return;
|
||
}
|
||
|
||
const baseList = Array.isArray(referenceData?.planetScience)
|
||
? referenceData.planetScience
|
||
: [];
|
||
|
||
if (baseList.length === 0) {
|
||
if (elements.planetDetailNameEl) {
|
||
elements.planetDetailNameEl.textContent = "Planet data unavailable";
|
||
}
|
||
if (elements.planetDetailSummaryEl) {
|
||
elements.planetDetailSummaryEl.textContent = "Could not load local science facts dataset.";
|
||
}
|
||
return;
|
||
}
|
||
|
||
const correspondences = referenceData?.planets && typeof referenceData.planets === "object"
|
||
? referenceData.planets
|
||
: {};
|
||
|
||
const correspondenceByName = Object.values(correspondences).reduce((acc, value) => {
|
||
const key = String(value?.name || "").trim().toLowerCase();
|
||
if (key) {
|
||
acc[key] = value;
|
||
}
|
||
return acc;
|
||
}, {});
|
||
|
||
state.kabbalahTargetsByPlanetId = buildKabbalahTargetsByPlanet(magickDataset);
|
||
state.monthRefsByPlanetId = buildMonthReferencesByPlanet(referenceData);
|
||
state.cubePlacementsByPlanetId = buildCubePlacementsByPlanet(magickDataset);
|
||
|
||
state.entries = baseList.map((entry) => {
|
||
const byId = correspondences[entry.id] || null;
|
||
const byName = correspondenceByName[String(entry?.name || "").trim().toLowerCase()] || null;
|
||
return {
|
||
...entry,
|
||
correspondence: byId || byName || null
|
||
};
|
||
});
|
||
state.filteredEntries = [...state.entries];
|
||
|
||
renderList(elements);
|
||
|
||
if (state.entries.length > 0) {
|
||
selectById(state.entries[0].id, elements);
|
||
}
|
||
|
||
elements.planetCardListEl.addEventListener("click", (event) => {
|
||
const target = event.target;
|
||
if (!(target instanceof Node)) {
|
||
return;
|
||
}
|
||
|
||
const button = target instanceof Element
|
||
? target.closest(".planet-list-item")
|
||
: null;
|
||
|
||
if (!(button instanceof HTMLButtonElement)) {
|
||
return;
|
||
}
|
||
|
||
const selectedId = button.dataset.planetId;
|
||
if (!selectedId) {
|
||
return;
|
||
}
|
||
|
||
selectById(selectedId, elements);
|
||
});
|
||
|
||
if (elements.planetSearchInputEl) {
|
||
elements.planetSearchInputEl.addEventListener("input", () => {
|
||
state.searchQuery = elements.planetSearchInputEl.value || "";
|
||
applySearchFilter(elements);
|
||
});
|
||
}
|
||
|
||
if (elements.planetSearchClearEl && elements.planetSearchInputEl) {
|
||
elements.planetSearchClearEl.addEventListener("click", () => {
|
||
elements.planetSearchInputEl.value = "";
|
||
state.searchQuery = "";
|
||
applySearchFilter(elements);
|
||
elements.planetSearchInputEl.focus();
|
||
});
|
||
}
|
||
|
||
state.initialized = true;
|
||
}
|
||
|
||
function selectByPlanetId(planetId) {
|
||
if (!state.initialized) return;
|
||
const el = getElements();
|
||
const needle = String(planetId || "").toLowerCase();
|
||
const entry = state.entries.find(e =>
|
||
String(e.id || "").toLowerCase() === needle ||
|
||
String(e.correspondence?.id || "").toLowerCase() === needle ||
|
||
String(e.name || "").toLowerCase() === needle
|
||
);
|
||
if (!entry) return;
|
||
selectById(entry.id, el);
|
||
el.planetCardListEl
|
||
?.querySelector(`[data-planet-id="${entry.id}"]`)
|
||
?.scrollIntoView({ block: "nearest" });
|
||
}
|
||
|
||
window.PlanetSectionUi = {
|
||
ensurePlanetSection,
|
||
selectByPlanetId
|
||
};
|
||
})();
|