Initial commit
This commit is contained in:
898
app/ui-planets.js
Normal file
898
app/ui-planets.js
Normal file
@@ -0,0 +1,898 @@
|
||||
(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
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user