Files
TaroTime/app/ui-tarot-house.js

227 lines
7.2 KiB
JavaScript
Raw Normal View History

2026-03-07 05:17:50 -08:00
(function () {
"use strict";
const HOUSE_MINOR_NUMBER_BANDS = [
[2, 3, 4],
[5, 6, 7],
[8, 9, 10],
[2, 3, 4],
[5, 6, 7],
[8, 9, 10]
];
const HOUSE_LEFT_SUITS = ["Wands", "Disks", "Swords", "Cups", "Wands", "Disks"];
const HOUSE_RIGHT_SUITS = ["Swords", "Cups", "Wands", "Disks", "Swords", "Cups"];
const HOUSE_MIDDLE_SUITS = ["Wands", "Cups", "Swords", "Disks"];
const HOUSE_MIDDLE_RANKS = ["Ace", "Knight", "Queen", "Prince", "Princess"];
const HOUSE_TRUMP_ROWS = [
[0],
[20, 21, 12],
[19, 10, 2, 1, 3, 16],
[18, 17, 15, 14, 13, 9, 8, 7, 6, 5, 4],
[11]
];
const config = {
resolveTarotCardImage: null,
getDisplayCardName: (card) => card?.name || "",
clearChildren: () => {},
normalizeTarotCardLookupName: (value) => String(value || "").trim().toLowerCase(),
selectCardById: () => {},
getCards: () => [],
getSelectedCardId: () => ""
};
function init(nextConfig = {}) {
Object.assign(config, nextConfig || {});
}
function getCardLookupMap(cards) {
const lookup = new Map();
(Array.isArray(cards) ? cards : []).forEach((card) => {
const key = config.normalizeTarotCardLookupName(card?.name);
if (key) {
lookup.set(key, card);
}
});
return lookup;
}
function buildMinorCardName(rankNumber, suit) {
const number = Number(rankNumber);
const suitName = String(suit || "").trim();
const rankName = ({ 1: "Ace", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine", 10: "Ten" })[number];
if (!rankName || !suitName) {
return "";
}
return `${rankName} of ${suitName}`;
}
function buildCourtCardName(rank, suit) {
const rankName = String(rank || "").trim();
const suitName = String(suit || "").trim();
if (!rankName || !suitName) {
return "";
}
return `${rankName} of ${suitName}`;
}
function findCardByLookupName(cardLookupMap, cardName) {
const key = config.normalizeTarotCardLookupName(cardName);
if (!key) {
return null;
}
return cardLookupMap.get(key) || null;
}
function findMajorCardByTrumpNumber(cards, trumpNumber) {
const target = Number(trumpNumber);
if (!Number.isFinite(target)) {
return null;
}
return (Array.isArray(cards) ? cards : []).find((card) => card?.arcana === "Major" && Number(card?.number) === target) || null;
}
function createHouseCardButton(card, elements) {
const button = document.createElement("button");
button.type = "button";
button.className = "tarot-house-card-btn";
if (!card) {
button.disabled = true;
const fallback = document.createElement("span");
fallback.className = "tarot-house-card-fallback";
fallback.textContent = "Missing";
button.appendChild(fallback);
return button;
}
const cardDisplayName = config.getDisplayCardName(card);
button.title = cardDisplayName || card.name;
button.setAttribute("aria-label", cardDisplayName || card.name);
button.dataset.houseCardId = card.id;
const imageUrl = typeof config.resolveTarotCardImage === "function"
? config.resolveTarotCardImage(card.name)
: null;
if (imageUrl) {
const image = document.createElement("img");
image.className = "tarot-house-card-image";
image.src = imageUrl;
image.alt = cardDisplayName || card.name;
button.appendChild(image);
} else {
const fallback = document.createElement("span");
fallback.className = "tarot-house-card-fallback";
fallback.textContent = cardDisplayName || card.name;
button.appendChild(fallback);
}
button.addEventListener("click", () => {
config.selectCardById(card.id, elements);
elements?.tarotCardListEl
?.querySelector(`[data-card-id="${card.id}"]`)
?.scrollIntoView({ block: "nearest" });
});
return button;
}
function updateSelection(elements) {
if (!elements?.tarotHouseOfCardsEl) {
return;
}
const selectedCardId = config.getSelectedCardId();
const buttons = elements.tarotHouseOfCardsEl.querySelectorAll(".tarot-house-card-btn[data-house-card-id]");
buttons.forEach((button) => {
const isSelected = button.dataset.houseCardId === selectedCardId;
button.classList.toggle("is-selected", isSelected);
button.setAttribute("aria-current", isSelected ? "true" : "false");
});
}
function appendHouseMinorRow(columnEl, cardLookupMap, numbers, suit, elements) {
const rowEl = document.createElement("div");
rowEl.className = "tarot-house-row";
numbers.forEach((rankNumber) => {
const cardName = buildMinorCardName(rankNumber, suit);
const card = findCardByLookupName(cardLookupMap, cardName);
rowEl.appendChild(createHouseCardButton(card, elements));
});
columnEl.appendChild(rowEl);
}
function appendHouseCourtRow(columnEl, cardLookupMap, rank, elements) {
const rowEl = document.createElement("div");
rowEl.className = "tarot-house-row";
HOUSE_MIDDLE_SUITS.forEach((suit) => {
const cardName = buildCourtCardName(rank, suit);
const card = findCardByLookupName(cardLookupMap, cardName);
rowEl.appendChild(createHouseCardButton(card, elements));
});
columnEl.appendChild(rowEl);
}
function appendHouseTrumpRow(containerEl, trumpNumbers, elements, cards) {
const rowEl = document.createElement("div");
rowEl.className = "tarot-house-trump-row";
(trumpNumbers || []).forEach((trumpNumber) => {
const card = findMajorCardByTrumpNumber(cards, trumpNumber);
rowEl.appendChild(createHouseCardButton(card, elements));
});
containerEl.appendChild(rowEl);
}
function render(elements) {
if (!elements?.tarotHouseOfCardsEl) {
return;
}
const cards = config.getCards();
config.clearChildren(elements.tarotHouseOfCardsEl);
const cardLookupMap = getCardLookupMap(cards);
const trumpSectionEl = document.createElement("div");
trumpSectionEl.className = "tarot-house-trumps";
HOUSE_TRUMP_ROWS.forEach((trumpRow) => {
appendHouseTrumpRow(trumpSectionEl, trumpRow, elements, cards);
});
const bottomGridEl = document.createElement("div");
bottomGridEl.className = "tarot-house-bottom-grid";
const leftColumnEl = document.createElement("div");
leftColumnEl.className = "tarot-house-column";
HOUSE_MINOR_NUMBER_BANDS.forEach((numbers, rowIndex) => {
appendHouseMinorRow(leftColumnEl, cardLookupMap, numbers, HOUSE_LEFT_SUITS[rowIndex], elements);
});
const middleColumnEl = document.createElement("div");
middleColumnEl.className = "tarot-house-column";
HOUSE_MIDDLE_RANKS.forEach((rank) => {
appendHouseCourtRow(middleColumnEl, cardLookupMap, rank, elements);
});
const rightColumnEl = document.createElement("div");
rightColumnEl.className = "tarot-house-column";
HOUSE_MINOR_NUMBER_BANDS.forEach((numbers, rowIndex) => {
appendHouseMinorRow(rightColumnEl, cardLookupMap, numbers, HOUSE_RIGHT_SUITS[rowIndex], elements);
});
bottomGridEl.append(leftColumnEl, middleColumnEl, rightColumnEl);
elements.tarotHouseOfCardsEl.append(trumpSectionEl, bottomGridEl);
updateSelection(elements);
}
window.TarotHouseUi = {
init,
render,
updateSelection
};
})();