Files
TaroTime/app/ui-planets.js
2026-03-07 13:38:13 -08:00

596 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
"use strict";
const { getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
const planetReferenceBuilders = window.PlanetReferenceBuilders || {};
if (
typeof planetReferenceBuilders.buildMonthReferencesByPlanet !== "function"
|| typeof planetReferenceBuilders.buildCubePlacementsByPlanet !== "function"
) {
throw new Error("PlanetReferenceBuilders module must load before ui-planets.js");
}
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 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 = planetReferenceBuilders.buildMonthReferencesByPlanet({
referenceData,
toPlanetId,
normalizePlanetToken
});
state.cubePlacementsByPlanetId = planetReferenceBuilders.buildCubePlacementsByPlanet({
magickDataset,
toPlanetId
});
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
};
})();