Files
TaroTime/app/ui-elements.js
2026-03-07 01:09:00 -08:00

455 lines
14 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 { getTarotCardSearchAliases } = window.TarotCardImages || {};
const CLASSICAL_ELEMENT_IDS = ["fire", "water", "air", "earth"];
const ACE_BY_ELEMENT_ID = {
water: "Ace of Cups",
fire: "Ace of Wands",
air: "Ace of Swords",
earth: "Ace of Disks"
};
const HEBREW_LETTER_NAME_BY_ELEMENT_ID = {
fire: "Yod",
water: "Heh",
air: "Vav",
earth: "Heh"
};
const HEBREW_LETTER_CHAR_BY_ELEMENT_ID = {
fire: "י",
water: "ה",
air: "ו",
earth: "ה"
};
const COURT_RANK_BY_ELEMENT_ID = {
fire: "Knight",
water: "Queen",
air: "Prince",
earth: "Princess"
};
const COURT_SUITS = ["Wands", "Cups", "Swords", "Disks"];
const SUIT_BY_ELEMENT_ID = {
fire: "Wands",
water: "Cups",
air: "Swords",
earth: "Disks"
};
const SMALL_CARD_GROUPS = [
{ label: "24", modality: "Cardinal", numbers: [2, 3, 4] },
{ label: "57", modality: "Fixed", numbers: [5, 6, 7] },
{ label: "810", modality: "Mutable", numbers: [8, 9, 10] }
];
const SIGN_BY_ELEMENT_AND_MODALITY = {
fire: { cardinal: "aries", fixed: "leo", mutable: "sagittarius" },
water: { cardinal: "cancer", fixed: "scorpio", mutable: "pisces" },
air: { cardinal: "libra", fixed: "aquarius", mutable: "gemini" },
earth: { cardinal: "capricorn", fixed: "taurus", mutable: "virgo" }
};
const state = {
initialized: false,
entries: [],
filteredEntries: [],
selectedId: "",
searchQuery: ""
};
function getElements() {
return {
listEl: document.getElementById("elements-list"),
countEl: document.getElementById("elements-count"),
searchEl: document.getElementById("elements-search-input"),
searchClearEl: document.getElementById("elements-search-clear"),
detailNameEl: document.getElementById("elements-detail-name"),
detailSubEl: document.getElementById("elements-detail-sub"),
detailBodyEl: document.getElementById("elements-detail-body")
};
}
function normalize(value) {
return String(value || "")
.trim()
.toLowerCase()
.replace(/\s+/g, " ");
}
function titleCase(value) {
return String(value || "")
.split(" ")
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}
function buildTarotAliasText(cardNames) {
if (typeof getTarotCardSearchAliases !== "function") {
return Array.isArray(cardNames) ? cardNames.join(" ") : "";
}
const aliases = new Set();
(Array.isArray(cardNames) ? cardNames : []).forEach((cardName) => {
getTarotCardSearchAliases(cardName).forEach((alias) => aliases.add(alias));
});
return Array.from(aliases).join(" ");
}
function buildSmallCardGroupsForElement(elementId) {
const suit = SUIT_BY_ELEMENT_ID[elementId] || "";
const signByModality = SIGN_BY_ELEMENT_AND_MODALITY[elementId] || {};
return SMALL_CARD_GROUPS.map((group) => {
const signId = String(signByModality[String(group.modality || "").toLowerCase()] || "").trim();
const signName = titleCase(signId);
const cardNames = group.numbers.map((number) => `${number} of ${suit}`);
return {
rangeLabel: group.label,
modality: group.modality,
signId,
signName,
cardNames
};
});
}
function buildEntries(magickDataset) {
const source = magickDataset?.grouped?.alchemy?.elements;
if (!source || typeof source !== "object") {
return [];
}
return CLASSICAL_ELEMENT_IDS
.map((id) => {
const item = source[id];
if (!item || typeof item !== "object") {
return null;
}
const name = String(item?.name?.en || item?.name || titleCase(id)).trim() || titleCase(id);
const symbol = String(item?.symbol || "").trim();
const aceCardName = ACE_BY_ELEMENT_ID[id] || "";
const hebrewLetter = HEBREW_LETTER_CHAR_BY_ELEMENT_ID[id] || "";
const hebrewLetterName = HEBREW_LETTER_NAME_BY_ELEMENT_ID[id] || "";
const courtRank = COURT_RANK_BY_ELEMENT_ID[id] || "";
const courtCardNames = courtRank
? COURT_SUITS.map((suit) => `${courtRank} of ${suit}`)
: [];
const smallCardGroups = buildSmallCardGroupsForElement(id);
const smallCardNames = smallCardGroups.flatMap((group) => group.cardNames || []);
const tarotAliasText = buildTarotAliasText([aceCardName, ...courtCardNames, ...smallCardNames]);
return {
id,
name,
symbol,
elementalId: String(item?.elementalId || "").trim(),
aceCardName,
hebrewLetter,
hebrewLetterName,
courtRank,
courtCardNames,
smallCardGroups,
searchText: normalize(`${id} ${name} ${symbol} ${aceCardName} ${hebrewLetter} ${hebrewLetterName} ${courtRank} ${courtCardNames.join(" ")} ${smallCardGroups.map((group) => `${group.modality} ${group.signName} ${group.cardNames.join(" ")}`).join(" ")} ${tarotAliasText}`)
};
})
.filter(Boolean);
}
function findEntryById(id) {
const normalizedId = normalize(id);
return state.entries.find((entry) => entry.id === normalizedId) || null;
}
function renderList(elements) {
if (!elements?.listEl) {
return;
}
elements.listEl.replaceChildren();
state.filteredEntries.forEach((entry) => {
const button = document.createElement("button");
button.type = "button";
button.className = "planet-list-item";
button.dataset.elementId = entry.id;
button.setAttribute("role", "option");
const isSelected = entry.id === state.selectedId;
button.classList.toggle("is-selected", isSelected);
button.setAttribute("aria-selected", isSelected ? "true" : "false");
const name = document.createElement("span");
name.className = "planet-list-name";
name.textContent = `${entry.symbol} ${entry.name}`.trim();
const meta = document.createElement("span");
meta.className = "planet-list-meta";
meta.textContent = `Letter: ${entry.hebrewLetter || "--"} · Ace: ${entry.aceCardName || "--"} · Court: ${entry.courtRank || "--"}`;
button.append(name, meta);
elements.listEl.appendChild(button);
});
if (elements.countEl) {
elements.countEl.textContent = `${state.filteredEntries.length} elements`;
}
if (!state.filteredEntries.length) {
const empty = document.createElement("div");
empty.className = "planet-text";
empty.style.padding = "16px";
empty.style.color = "#71717a";
empty.textContent = "No elements match your search.";
elements.listEl.appendChild(empty);
}
}
function renderDetail(elements) {
if (!elements?.detailNameEl || !elements.detailSubEl || !elements.detailBodyEl) {
return;
}
const entry = findEntryById(state.selectedId);
elements.detailBodyEl.replaceChildren();
if (!entry) {
elements.detailNameEl.textContent = "--";
elements.detailSubEl.textContent = "Select an element to explore";
return;
}
elements.detailNameEl.textContent = `${entry.symbol} ${entry.name}`.trim();
elements.detailSubEl.textContent = "Classical Element";
const grid = document.createElement("div");
grid.className = "planet-meta-grid";
const detailsCard = document.createElement("div");
detailsCard.className = "planet-meta-card";
detailsCard.innerHTML = `
<strong>Element Details</strong>
<dl class="alpha-dl">
<dt>Name</dt><dd>${entry.name}</dd>
<dt>Symbol</dt><dd>${entry.symbol || "--"}</dd>
<dt>Hebrew Letter</dt><dd>${entry.hebrewLetter || "--"}</dd>
<dt>Court Rank</dt><dd>${entry.courtRank || "--"}</dd>
<dt>ID</dt><dd>${entry.id}</dd>
</dl>
`;
const tarotCard = document.createElement("div");
tarotCard.className = "planet-meta-card";
const tarotTitle = document.createElement("strong");
tarotTitle.textContent = "Tarot Correspondence";
const tarotText = document.createElement("div");
tarotText.className = "planet-text";
tarotText.textContent = [
entry.aceCardName ? `Ace: ${entry.aceCardName}` : "",
entry.courtRank ? `Court Rank: ${entry.courtRank} (all suits)` : ""
].filter(Boolean).join(" · ") || "--";
tarotCard.append(tarotTitle, tarotText);
if (entry.aceCardName || entry.courtCardNames.length) {
const navWrap = document.createElement("div");
navWrap.className = "alpha-nav-btns";
if (entry.aceCardName) {
const tarotBtn = document.createElement("button");
tarotBtn.type = "button";
tarotBtn.className = "alpha-nav-btn";
tarotBtn.textContent = `Open ${entry.aceCardName}`;
tarotBtn.addEventListener("click", () => {
document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
detail: { cardName: entry.aceCardName }
}));
});
navWrap.appendChild(tarotBtn);
}
entry.courtCardNames.forEach((cardName) => {
const courtBtn = document.createElement("button");
courtBtn.type = "button";
courtBtn.className = "alpha-nav-btn";
courtBtn.textContent = `Open ${cardName}`;
courtBtn.addEventListener("click", () => {
document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
detail: { cardName }
}));
});
navWrap.appendChild(courtBtn);
});
tarotCard.appendChild(navWrap);
}
const smallCardCard = document.createElement("div");
smallCardCard.className = "planet-meta-card";
const smallCardTitle = document.createElement("strong");
smallCardTitle.textContent = "Small Card Sign Types";
smallCardCard.appendChild(smallCardTitle);
const smallCardStack = document.createElement("div");
smallCardStack.className = "cal-item-stack";
(entry.smallCardGroups || []).forEach((group) => {
const row = document.createElement("div");
row.className = "cal-item-row";
const head = document.createElement("div");
head.className = "cal-item-head";
head.innerHTML = `
<span class="cal-item-name">${group.rangeLabel} · ${group.modality}</span>
<span class="planet-list-meta">${group.signName || "--"}</span>
`;
row.appendChild(head);
const navWrap = document.createElement("div");
navWrap.className = "alpha-nav-btns";
if (group.signId) {
const signBtn = document.createElement("button");
signBtn.type = "button";
signBtn.className = "alpha-nav-btn";
signBtn.textContent = `Open ${group.signName}`;
signBtn.addEventListener("click", () => {
document.dispatchEvent(new CustomEvent("nav:zodiac", {
detail: { signId: group.signId }
}));
});
navWrap.appendChild(signBtn);
}
(group.cardNames || []).forEach((cardName) => {
const cardBtn = document.createElement("button");
cardBtn.type = "button";
cardBtn.className = "alpha-nav-btn";
cardBtn.textContent = `Open ${cardName}`;
cardBtn.addEventListener("click", () => {
document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
detail: { cardName }
}));
});
navWrap.appendChild(cardBtn);
});
row.appendChild(navWrap);
smallCardStack.appendChild(row);
});
smallCardCard.appendChild(smallCardStack);
grid.append(detailsCard, tarotCard, smallCardCard);
elements.detailBodyEl.appendChild(grid);
}
function applyFilter(elements) {
const query = normalize(state.searchQuery);
state.filteredEntries = query
? state.entries.filter((entry) => entry.searchText.includes(query))
: [...state.entries];
if (elements?.searchClearEl) {
elements.searchClearEl.disabled = !query;
}
if (!state.filteredEntries.some((entry) => entry.id === state.selectedId)) {
state.selectedId = state.filteredEntries[0]?.id || "";
}
renderList(elements);
renderDetail(elements);
}
function selectByElementId(elementId) {
const target = findEntryById(elementId);
if (!target) {
return false;
}
const elements = getElements();
state.selectedId = target.id;
renderList(elements);
renderDetail(elements);
const listItem = elements.listEl?.querySelector(`[data-element-id="${target.id}"]`);
listItem?.scrollIntoView({ block: "nearest" });
return true;
}
function ensureElementsSection(magickDataset) {
const elements = getElements();
if (!elements.listEl || !elements.detailBodyEl) {
return;
}
state.entries = buildEntries(magickDataset);
if (!state.selectedId && state.entries.length) {
state.selectedId = state.entries[0].id;
}
applyFilter(elements);
if (state.initialized) {
return;
}
elements.listEl.addEventListener("click", (event) => {
const target = event.target instanceof Element
? event.target.closest(".planet-list-item")
: null;
if (!(target instanceof HTMLButtonElement)) {
return;
}
const elementId = target.dataset.elementId;
if (!elementId) {
return;
}
state.selectedId = elementId;
renderList(elements);
renderDetail(elements);
});
if (elements.searchEl) {
elements.searchEl.addEventListener("input", () => {
state.searchQuery = elements.searchEl.value || "";
applyFilter(elements);
});
}
if (elements.searchClearEl && elements.searchEl) {
elements.searchClearEl.addEventListener("click", () => {
state.searchQuery = "";
elements.searchEl.value = "";
applyFilter(elements);
elements.searchEl.focus();
});
}
state.initialized = true;
}
window.ElementsSectionUi = {
ensureElementsSection,
selectByElementId
};
})();