(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 }; })();