227 lines
7.2 KiB
JavaScript
227 lines
7.2 KiB
JavaScript
|
|
(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
|
||
|
|
};
|
||
|
|
})();
|