(function () { const dataService = window.TarotDataService || {}; const { resolveTarotCardImage, resolveTarotCardThumbnail, getTarotCardDisplayName, getTarotCardSearchAliases, getDeckOptions, getActiveDeck } = window.TarotCardImages || {}; const tarotHouseUi = window.TarotHouseUi || {}; const tarotRelationsUi = window.TarotRelationsUi || {}; const tarotCardDerivations = window.TarotCardDerivations || {}; const tarotDetailUi = window.TarotDetailUi || {}; const tarotRelationDisplay = window.TarotRelationDisplay || {}; const state = { initialized: false, cards: [], filteredCards: [], searchQuery: "", selectedCardId: "", houseFocusMode: false, houseTopCardsVisible: true, houseTopInfoModes: { hebrew: true, planet: true, zodiac: true, trump: true, path: true }, houseBottomCardsVisible: true, houseBottomInfoModes: { zodiac: true, decan: true, month: true, ruler: true, date: false }, houseExportInProgress: false, houseExportFormat: "png", magickDataset: null, referenceData: null, monthRefsByCardId: new Map(), courtCardByDecanId: new Map(), loadingPromise: null }; const TAROT_TRUMP_NUMBER_BY_NAME = { "the fool": 0, fool: 0, "the magus": 1, magus: 1, magician: 1, "the high priestess": 2, "high priestess": 2, "the empress": 3, empress: 3, "the emperor": 4, emperor: 4, "the hierophant": 5, hierophant: 5, "the lovers": 6, lovers: 6, "the chariot": 7, chariot: 7, strength: 8, lust: 8, "the hermit": 9, hermit: 9, fortune: 10, "wheel of fortune": 10, justice: 11, "the hanged man": 12, "hanged man": 12, death: 13, temperance: 14, art: 14, "the devil": 15, devil: 15, "the tower": 16, tower: 16, "the star": 17, star: 17, "the moon": 18, moon: 18, "the sun": 19, sun: 19, aeon: 20, judgement: 20, judgment: 20, universe: 21, world: 21, "the world": 21 }; const MINOR_NUMBER_WORD_BY_VALUE = { 1: "ace", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine", 10: "ten" }; const HEBREW_LETTER_ALIASES = { aleph: "alef", alef: "alef", heh: "he", he: "he", beth: "bet", bet: "bet", cheth: "het", chet: "het", kaph: "kaf", kaf: "kaf", peh: "pe", tzaddi: "tsadi", tzadi: "tsadi", tsadi: "tsadi", qoph: "qof", qof: "qof", taw: "tav", tau: "tav" }; const CUBE_MOTHER_CONNECTOR_BY_LETTER = { alef: { connectorId: "above-below", connectorName: "Above ↔ Below" }, mem: { connectorId: "east-west", connectorName: "East ↔ West" }, shin: { connectorId: "south-north", connectorName: "South ↔ North" } }; const ELEMENT_NAME_BY_ID = { water: "Water", fire: "Fire", air: "Air", earth: "Earth" }; const ELEMENT_HEBREW_LETTER_BY_ID = { fire: "Yod", water: "Heh", air: "Vav", earth: "Heh" }; const ELEMENT_HEBREW_CHAR_BY_ID = { fire: "י", water: "ה", air: "ו", earth: "ה" }; const HEBREW_LETTER_ID_BY_TETRAGRAMMATON_LETTER = { yod: "yod", heh: "he", vav: "vav" }; const ACE_ELEMENT_BY_CARD_NAME = { "ace of cups": "water", "ace of wands": "fire", "ace of swords": "air", "ace of disks": "earth" }; const COURT_ELEMENT_BY_RANK = { knight: "fire", queen: "water", prince: "air", princess: "earth" }; const SUIT_ELEMENT_BY_SUIT = { wands: "fire", cups: "water", swords: "air", disks: "earth" }; const MINOR_RANK_NUMBER_BY_NAME = { ace: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10 }; const SMALL_CARD_SIGN_BY_MODALITY_AND_SUIT = { cardinal: { wands: "aries", cups: "cancer", swords: "libra", disks: "capricorn" }, fixed: { wands: "leo", cups: "scorpio", swords: "aquarius", disks: "taurus" }, mutable: { wands: "sagittarius", cups: "pisces", swords: "gemini", disks: "virgo" } }; const MINOR_PLURAL_BY_RANK = { ace: "aces", two: "twos", three: "threes", four: "fours", five: "fives", six: "sixes", seven: "sevens", eight: "eights", nine: "nines", ten: "tens" }; function slugify(value) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, ""); } function cardId(card) { const suitPart = card.suit ? `-${slugify(card.suit)}` : ""; return `${slugify(card.arcana)}${suitPart}-${slugify(card.name)}`; } function getElements() { return { tarotSectionEl: document.getElementById("tarot-section"), tarotHouseSectionEl: document.getElementById("tarot-house-section"), tarotCardListEl: document.getElementById("tarot-card-list"), tarotSearchInputEl: document.getElementById("tarot-search-input"), tarotSearchClearEl: document.getElementById("tarot-search-clear"), tarotCountEl: document.getElementById("tarot-card-count"), tarotDetailImageEl: document.getElementById("tarot-detail-image"), tarotDetailNameEl: document.getElementById("tarot-detail-name"), tarotDetailTypeEl: document.getElementById("tarot-detail-type"), tarotDetailSummaryEl: document.getElementById("tarot-detail-summary"), tarotDetailUprightEl: document.getElementById("tarot-detail-upright"), tarotDetailReversedEl: document.getElementById("tarot-detail-reversed"), tarotMetaMeaningCardEl: document.getElementById("tarot-meta-meaning-card"), tarotDetailMeaningEl: document.getElementById("tarot-detail-meaning"), tarotDetailKeywordsEl: document.getElementById("tarot-detail-keywords"), tarotMetaPlanetCardEl: document.getElementById("tarot-meta-planet-card"), tarotMetaElementCardEl: document.getElementById("tarot-meta-element-card"), tarotMetaTetragrammatonCardEl: document.getElementById("tarot-meta-tetragrammaton-card"), tarotMetaZodiacCardEl: document.getElementById("tarot-meta-zodiac-card"), tarotMetaCourtDateCardEl: document.getElementById("tarot-meta-courtdate-card"), tarotMetaHebrewCardEl: document.getElementById("tarot-meta-hebrew-card"), tarotMetaCubeCardEl: document.getElementById("tarot-meta-cube-card"), tarotMetaIChingCardEl: document.getElementById("tarot-meta-iching-card"), tarotMetaCalendarCardEl: document.getElementById("tarot-meta-calendar-card"), tarotDetailPlanetEl: document.getElementById("tarot-detail-planet"), tarotDetailElementEl: document.getElementById("tarot-detail-element"), tarotDetailTetragrammatonEl: document.getElementById("tarot-detail-tetragrammaton"), tarotDetailZodiacEl: document.getElementById("tarot-detail-zodiac"), tarotDetailCourtDateEl: document.getElementById("tarot-detail-courtdate"), tarotDetailHebrewEl: document.getElementById("tarot-detail-hebrew"), tarotDetailCubeEl: document.getElementById("tarot-detail-cube"), tarotDetailIChingEl: document.getElementById("tarot-detail-iching"), tarotDetailCalendarEl: document.getElementById("tarot-detail-calendar"), tarotKabPathEl: document.getElementById("tarot-kab-path"), tarotHouseOfCardsEl: document.getElementById("tarot-house-of-cards"), tarotBrowseViewEl: document.getElementById("tarot-browse-view"), tarotHouseViewEl: document.getElementById("tarot-house-view"), tarotHouseTopCardsVisibleEl: document.getElementById("tarot-house-top-cards-visible"), tarotHouseTopInfoHebrewEl: document.getElementById("tarot-house-top-info-hebrew"), tarotHouseTopInfoPlanetEl: document.getElementById("tarot-house-top-info-planet"), tarotHouseTopInfoZodiacEl: document.getElementById("tarot-house-top-info-zodiac"), tarotHouseTopInfoTrumpEl: document.getElementById("tarot-house-top-info-trump"), tarotHouseTopInfoPathEl: document.getElementById("tarot-house-top-info-path"), tarotHouseBottomCardsVisibleEl: document.getElementById("tarot-house-bottom-cards-visible"), tarotHouseBottomInfoZodiacEl: document.getElementById("tarot-house-bottom-info-zodiac"), tarotHouseBottomInfoDecanEl: document.getElementById("tarot-house-bottom-info-decan"), tarotHouseBottomInfoMonthEl: document.getElementById("tarot-house-bottom-info-month"), tarotHouseBottomInfoRulerEl: document.getElementById("tarot-house-bottom-info-ruler"), tarotHouseBottomInfoDateEl: document.getElementById("tarot-house-bottom-info-date"), tarotHouseFocusToggleEl: document.getElementById("tarot-house-focus-toggle"), tarotHouseExportEl: document.getElementById("tarot-house-export"), tarotHouseExportWebpEl: document.getElementById("tarot-house-export-webp") }; } function setHouseBottomInfoCheckboxState(checkbox, enabled) { if (!checkbox) { return; } checkbox.checked = Boolean(enabled); checkbox.disabled = Boolean(state.houseExportInProgress); } function normalizeRelationId(value) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, ""); } if (typeof tarotRelationDisplay.createTarotRelationDisplay !== "function") { throw new Error("TarotRelationDisplay.createTarotRelationDisplay is unavailable. Ensure app/ui-tarot-relation-display.js loads before app/ui-tarot.js."); } if (typeof tarotCardDerivations.createTarotCardDerivations !== "function") { throw new Error("TarotCardDerivations.createTarotCardDerivations is unavailable. Ensure app/ui-tarot-card-derivations.js loads before app/ui-tarot.js."); } if (typeof tarotDetailUi.createTarotDetailRenderer !== "function") { throw new Error("TarotDetailUi.createTarotDetailRenderer is unavailable. Ensure app/ui-tarot-detail.js loads before app/ui-tarot.js."); } const tarotCardDerivationsUi = tarotCardDerivations.createTarotCardDerivations({ normalizeRelationId, normalizeTarotCardLookupName, toTitleCase, getReferenceData: () => state.referenceData, ELEMENT_NAME_BY_ID, ELEMENT_HEBREW_LETTER_BY_ID, ELEMENT_HEBREW_CHAR_BY_ID, HEBREW_LETTER_ID_BY_TETRAGRAMMATON_LETTER, ACE_ELEMENT_BY_CARD_NAME, COURT_ELEMENT_BY_RANK, MINOR_RANK_NUMBER_BY_NAME, SMALL_CARD_SIGN_BY_MODALITY_AND_SUIT, MINOR_PLURAL_BY_RANK }); const tarotRelationDisplayUi = tarotRelationDisplay.createTarotRelationDisplay({ normalizeRelationId }); const tarotDetailRenderer = tarotDetailUi.createTarotDetailRenderer({ getMonthRefsByCardId: () => state.monthRefsByCardId, getMagickDataset: () => state.magickDataset, resolveTarotCardImage, resolveTarotCardThumbnail, getDisplayCardName, buildTypeLabel, clearChildren, normalizeRelationObject, buildElementRelationsForCard, buildTetragrammatonRelationsForCard, buildSmallCardRulershipRelation, buildSmallCardCourtLinkRelations, buildCubeRelationsForCard, buildIChingRelationsForCard, parseMonthDayToken, createRelationListItem, findSephirahForMinorCard }); function normalizeSearchValue(value) { return String(value || "").trim().toLowerCase(); } function normalizeTarotName(value) { return String(value || "") .trim() .toLowerCase() .replace(/\s+/g, " "); } function normalizeTarotCardLookupName(value) { const text = normalizeTarotName(value) .replace(/\b(pentacles?|coins?)\b/g, "disks"); const match = text.match(/^(\d{1,2})\s+of\s+(.+)$/i); if (!match) { return text; } const numeric = Number(match[1]); const suit = String(match[2] || "").trim(); const rankWord = MINOR_NUMBER_WORD_BY_VALUE[numeric]; if (!rankWord || !suit) { return text; } return `${rankWord} of ${suit}`; } function getDisplayCardName(cardOrName, trumpNumber) { const cardName = typeof cardOrName === "object" ? String(cardOrName?.name || "") : String(cardOrName || ""); const resolvedTrumpNumber = typeof cardOrName === "object" ? cardOrName?.number : trumpNumber; if (typeof getTarotCardDisplayName === "function") { const display = String(getTarotCardDisplayName(cardName, { trumpNumber: resolvedTrumpNumber }) || "").trim(); if (display) { return display; } } return cardName.trim(); } function toTitleCase(value) { return String(value || "") .split(" ") .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); } function buildElementRelationsForCard(card, baseElementRelations = []) { return tarotCardDerivationsUi.buildElementRelationsForCard(card, baseElementRelations); } function buildTetragrammatonRelationsForCard(card) { return tarotCardDerivationsUi.buildTetragrammatonRelationsForCard(card); } function buildSmallCardRulershipRelation(card) { return tarotCardDerivationsUi.buildSmallCardRulershipRelation(card); } function buildCourtCardByDecanId(cards) { if (typeof tarotRelationsUi.buildCourtCardByDecanId !== "function") { return new Map(); } return tarotRelationsUi.buildCourtCardByDecanId(cards); } function buildSmallCardCourtLinkRelations(card, relations) { if (typeof tarotRelationsUi.buildSmallCardCourtLinkRelations !== "function") { return []; } return tarotRelationsUi.buildSmallCardCourtLinkRelations(card, relations, state.courtCardByDecanId); } function parseMonthDayToken(value) { if (typeof tarotRelationsUi.parseMonthDayToken !== "function") { return null; } return tarotRelationsUi.parseMonthDayToken(value); } function buildMonthReferencesByCard(referenceData, cards) { if (typeof tarotRelationsUi.buildMonthReferencesByCard !== "function") { return new Map(); } return tarotRelationsUi.buildMonthReferencesByCard(referenceData, cards); } function relationToSearchText(relation) { if (!relation) { return ""; } if (typeof relation === "string") { return relation; } const relationParts = [ relation.label, relation.type, relation.id, relation.data && typeof relation.data === "object" ? Object.values(relation.data).join(" ") : "" ]; return relationParts.filter(Boolean).join(" "); } function buildCardSearchText(card) { const displayName = getDisplayCardName(card); const tarotAliases = typeof getTarotCardSearchAliases === "function" ? getTarotCardSearchAliases(card?.name, { trumpNumber: card?.number }) : []; const parts = [ card.name, displayName, card.arcana, card.rank, card.suit, card.summary, ...tarotAliases, ...(Array.isArray(card.keywords) ? card.keywords : []), ...(Array.isArray(card.relations) ? card.relations.map(relationToSearchText) : []) ]; return normalizeSearchValue(parts.join(" ")); } function applySearchFilter(elements) { const query = normalizeSearchValue(state.searchQuery); state.filteredCards = query ? state.cards.filter((card) => buildCardSearchText(card).includes(query)) : [...state.cards]; if (elements?.tarotSearchClearEl) { elements.tarotSearchClearEl.disabled = !query; } renderList(elements); if (!state.filteredCards.some((card) => card.id === state.selectedCardId)) { if (state.filteredCards.length > 0) { selectCardById(state.filteredCards[0].id, elements); } return; } updateListSelection(elements); } function clearChildren(element) { if (element) { element.replaceChildren(); } } function updateHouseSelection(elements) { tarotHouseUi.updateSelection?.(elements); } function renderHouseOfCards(elements) { tarotHouseUi.render?.(elements); } function syncHouseControls(elements) { if (elements?.tarotHouseViewEl) { elements.tarotHouseViewEl.classList.toggle("is-house-focus", Boolean(state.houseFocusMode)); } if (elements?.tarotHouseTopCardsVisibleEl) { elements.tarotHouseTopCardsVisibleEl.checked = Boolean(state.houseTopCardsVisible); elements.tarotHouseTopCardsVisibleEl.disabled = Boolean(state.houseExportInProgress); } setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoHebrewEl, state.houseTopInfoModes.hebrew); setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoPlanetEl, state.houseTopInfoModes.planet); setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoZodiacEl, state.houseTopInfoModes.zodiac); setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoTrumpEl, state.houseTopInfoModes.trump); setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoPathEl, state.houseTopInfoModes.path); if (elements?.tarotHouseBottomCardsVisibleEl) { elements.tarotHouseBottomCardsVisibleEl.checked = Boolean(state.houseBottomCardsVisible); elements.tarotHouseBottomCardsVisibleEl.disabled = Boolean(state.houseExportInProgress); } setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoZodiacEl, state.houseBottomInfoModes.zodiac); setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoDecanEl, state.houseBottomInfoModes.decan); setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoMonthEl, state.houseBottomInfoModes.month); setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoRulerEl, state.houseBottomInfoModes.ruler); setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoDateEl, state.houseBottomInfoModes.date); if (elements?.tarotHouseFocusToggleEl) { elements.tarotHouseFocusToggleEl.setAttribute("aria-pressed", state.houseFocusMode ? "true" : "false"); elements.tarotHouseFocusToggleEl.textContent = state.houseFocusMode ? "Show Full Tarot" : "Focus House"; } if (elements?.tarotHouseExportEl) { elements.tarotHouseExportEl.disabled = Boolean(state.houseExportInProgress); elements.tarotHouseExportEl.textContent = state.houseExportInProgress ? "Exporting..." : "Export PNG"; } if (elements?.tarotHouseExportWebpEl) { const supportsWebp = tarotHouseUi.isExportFormatSupported?.("webp") === true; elements.tarotHouseExportWebpEl.disabled = Boolean(state.houseExportInProgress) || !supportsWebp; elements.tarotHouseExportWebpEl.hidden = !supportsWebp; elements.tarotHouseExportWebpEl.textContent = state.houseExportInProgress && state.houseExportFormat === "webp" ? "Exporting..." : "Export WebP"; if (supportsWebp) { elements.tarotHouseExportWebpEl.title = "Smaller file size, but not guaranteed lossless like PNG."; } } } async function exportHouseOfCards(elements, format = "png") { if (state.houseExportInProgress) { return; } state.houseExportInProgress = true; state.houseExportFormat = format; syncHouseControls(elements); try { await tarotHouseUi.exportImage?.(format); } catch (error) { window.alert(error instanceof Error ? error.message : "Unable to export the House of Cards image."); } finally { state.houseExportInProgress = false; state.houseExportFormat = "png"; syncHouseControls(elements); } } function buildTypeLabel(card) { return tarotCardDerivationsUi.buildTypeLabel(card); } function findSephirahForMinorCard(card, kabTree) { return tarotCardDerivationsUi.findSephirahForMinorCard(card, kabTree); } function formatRelation(relation) { return tarotRelationDisplayUi.formatRelation(relation); } function relationKey(relation, index) { return tarotRelationDisplayUi.relationKey(relation, index); } function normalizeRelationObject(relation, index) { return tarotRelationDisplayUi.normalizeRelationObject(relation, index); } function formatRelationDataLines(relation) { return tarotRelationDisplayUi.formatRelationDataLines(relation); } function buildCubeRelationsForCard(card) { if (typeof tarotRelationsUi.buildCubeRelationsForCard !== "function") { return []; } return tarotRelationsUi.buildCubeRelationsForCard(card, state.magickDataset); } function buildIChingRelationsForCard(card) { if (typeof tarotRelationsUi.buildIChingRelationsForCard !== "function") { return []; } return tarotRelationsUi.buildIChingRelationsForCard(card, state.referenceData); } // Returns nav dispatch config for relations that have a corresponding section, // null for informational-only relations. function getRelationNavTarget(relation) { return tarotRelationDisplayUi.getRelationNavTarget(relation); } function createRelationListItem(relation) { return tarotRelationDisplayUi.createRelationListItem(relation); } function renderStaticRelationGroup(targetEl, cardEl, relations) { tarotDetailRenderer.renderStaticRelationGroup(targetEl, cardEl, relations); } function renderDetail(card, elements) { tarotDetailRenderer.renderDetail(card, elements); } function updateListSelection(elements) { if (!elements?.tarotCardListEl) { return; } const buttons = elements.tarotCardListEl.querySelectorAll(".tarot-list-item"); buttons.forEach((button) => { const isSelected = button.dataset.cardId === state.selectedCardId; button.classList.toggle("is-selected", isSelected); button.setAttribute("aria-selected", isSelected ? "true" : "false"); }); } function selectCardById(cardIdToSelect, elements) { const card = state.cards.find((entry) => entry.id === cardIdToSelect); if (!card) { return; } state.selectedCardId = card.id; updateListSelection(elements); updateHouseSelection(elements); renderDetail(card, elements); } function scrollCardIntoView(cardIdToReveal, elements) { elements?.tarotCardListEl ?.querySelector(`[data-card-id="${cardIdToReveal}"]`) ?.scrollIntoView({ block: "nearest" }); } function getRegisteredDeckOptionMap() { const entries = typeof getDeckOptions === "function" ? getDeckOptions() : []; return new Map( (Array.isArray(entries) ? entries : []) .map((entry) => ({ id: String(entry?.id || "").trim(), label: String(entry?.label || entry?.id || "").trim() })) .filter((entry) => entry.id) .map((entry) => [entry.id, entry]) ); } function getRegisteredDeckList() { return Array.from(getRegisteredDeckOptionMap().values()); } function buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve = "") { const card = state.cards.find((entry) => entry.id === cardIdToResolve); if (!card) { return null; } const resolvedDeckId = String(deckIdToResolve || getActiveDeck?.() || "").trim(); const trumpNumber = Number.isFinite(Number(card?.number)) ? Number(card.number) : undefined; const deckOptions = resolvedDeckId ? { deckId: resolvedDeckId, trumpNumber } : { trumpNumber }; const src = typeof resolveTarotCardImage === "function" ? resolveTarotCardImage(card.name, deckOptions) : ""; const deckMeta = resolvedDeckId ? getRegisteredDeckOptionMap().get(resolvedDeckId) : null; const label = (typeof getTarotCardDisplayName === "function" ? getTarotCardDisplayName(card.name, deckOptions) : "") || getDisplayCardName(card) || card.name || "Tarot card enlarged image"; return { src, altText: label, label, cardId: card.id, deckId: resolvedDeckId, deckLabel: deckMeta?.label || resolvedDeckId, compareDetails: tarotDetailRenderer.buildCompareDetails?.(card) || [] }; } function buildLightboxCardRequestById(cardIdToResolve) { const request = buildDeckLightboxCardRequest(cardIdToResolve, getActiveDeck?.() || ""); if (!request?.src) { return null; } return request; } function renderList(elements) { if (!elements?.tarotCardListEl) { return; } clearChildren(elements.tarotCardListEl); state.filteredCards.forEach((card) => { const cardDisplayName = getDisplayCardName(card); const button = document.createElement("button"); button.type = "button"; button.className = "tarot-list-item"; button.dataset.cardId = card.id; button.setAttribute("role", "option"); const nameEl = document.createElement("span"); nameEl.className = "tarot-list-name"; nameEl.textContent = cardDisplayName || card.name; const metaEl = document.createElement("span"); metaEl.className = "tarot-list-meta"; metaEl.textContent = buildTypeLabel(card); button.append(nameEl, metaEl); elements.tarotCardListEl.appendChild(button); }); if (elements.tarotCountEl) { elements.tarotCountEl.textContent = state.searchQuery ? `${state.filteredCards.length} of ${state.cards.length} cards` : `${state.cards.length} cards`; } } async function loadCards(referenceData, magickDataset) { const payload = await dataService.loadTarotCards?.(); const cards = Array.isArray(payload?.cards) ? payload.cards : (Array.isArray(payload) ? payload : []); return cards.map((card) => ({ ...card, id: String(card?.id || "").trim() || cardId(card) })); } async function ensureTarotSection(referenceData, magickDataset = null) { state.referenceData = referenceData || state.referenceData; if (magickDataset) { state.magickDataset = magickDataset; } tarotHouseUi.init?.({ resolveTarotCardImage, resolveTarotCardThumbnail, getDisplayCardName, clearChildren, normalizeTarotCardLookupName, selectCardById, openCardLightbox: (src, altText, options = {}) => { const cardId = String(options?.cardId || "").trim(); const primaryCardRequest = cardId ? buildLightboxCardRequestById(cardId) : null; const activeDeckId = String(getActiveDeck?.() || primaryCardRequest?.deckId || "").trim(); const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeDeckId); window.TarotUiLightbox?.open?.({ src: primaryCardRequest?.src || src, altText: primaryCardRequest?.altText || altText || "Tarot card enlarged image", label: primaryCardRequest?.label || altText || "Tarot card enlarged image", cardId: primaryCardRequest?.cardId || cardId, deckId: primaryCardRequest?.deckId || activeDeckId, deckLabel: primaryCardRequest?.deckLabel || "", compareDetails: primaryCardRequest?.compareDetails || [], allowOverlayCompare: true, allowDeckCompare: Boolean(cardId), activeDeckId, activeDeckLabel: primaryCardRequest?.deckLabel || "", availableCompareDecks, maxCompareDecks: 2, sequenceIds: state.cards.map((card) => card.id), resolveCardById: buildLightboxCardRequestById, resolveDeckCardById: buildDeckLightboxCardRequest, onSelectCardId: (nextCardId) => { const latestElements = getElements(); selectCardById(nextCardId, latestElements); scrollCardIntoView(nextCardId, latestElements); } }); }, shouldOpenCardLightboxOnSelect: (latestElements) => Boolean( latestElements?.tarotHouseSectionEl instanceof HTMLElement && latestElements.tarotHouseSectionEl.hidden === false ), isHouseFocusMode: () => state.houseFocusMode, getCards: () => state.cards, getSelectedCardId: () => state.selectedCardId, getHouseTopCardsVisible: () => state.houseTopCardsVisible, getHouseTopInfoModes: () => ({ ...state.houseTopInfoModes }), getHouseBottomCardsVisible: () => state.houseBottomCardsVisible, getHouseBottomInfoModes: () => ({ ...state.houseBottomInfoModes }) }); const elements = getElements(); if (state.initialized) { state.monthRefsByCardId = buildMonthReferencesByCard(referenceData, state.cards); state.courtCardByDecanId = buildCourtCardByDecanId(state.cards); renderHouseOfCards(elements); syncHouseControls(elements); if (state.selectedCardId) { const selected = state.cards.find((card) => card.id === state.selectedCardId); if (selected) { renderDetail(selected, elements); } } return; } if (!elements.tarotCardListEl || !elements.tarotDetailNameEl) { return; } if (state.loadingPromise) { await state.loadingPromise; return; } state.loadingPromise = (async () => { const cards = await loadCards(referenceData, magickDataset); state.cards = cards; state.monthRefsByCardId = buildMonthReferencesByCard(referenceData, cards); state.courtCardByDecanId = buildCourtCardByDecanId(cards); state.filteredCards = [...cards]; renderList(elements); renderHouseOfCards(elements); syncHouseControls(elements); if (cards.length > 0) { selectCardById(cards[0].id, elements); } elements.tarotCardListEl.addEventListener("click", (event) => { const target = event.target; if (!(target instanceof Node)) { return; } const button = target instanceof Element ? target.closest(".tarot-list-item") : null; if (!(button instanceof HTMLButtonElement)) { return; } const selectedId = button.dataset.cardId; if (!selectedId) { return; } selectCardById(selectedId, elements); }); if (elements.tarotSearchInputEl) { elements.tarotSearchInputEl.addEventListener("input", () => { state.searchQuery = elements.tarotSearchInputEl.value || ""; applySearchFilter(elements); }); } if (elements.tarotSearchClearEl && elements.tarotSearchInputEl) { elements.tarotSearchClearEl.addEventListener("click", () => { elements.tarotSearchInputEl.value = ""; state.searchQuery = ""; applySearchFilter(elements); elements.tarotSearchInputEl.focus(); }); } if (elements.tarotHouseFocusToggleEl) { elements.tarotHouseFocusToggleEl.addEventListener("click", () => { state.houseFocusMode = !state.houseFocusMode; syncHouseControls(elements); }); } if (elements.tarotHouseTopCardsVisibleEl) { elements.tarotHouseTopCardsVisibleEl.addEventListener("change", () => { state.houseTopCardsVisible = Boolean(elements.tarotHouseTopCardsVisibleEl.checked); renderHouseOfCards(elements); syncHouseControls(elements); }); } [ [elements.tarotHouseTopInfoHebrewEl, "hebrew"], [elements.tarotHouseTopInfoPlanetEl, "planet"], [elements.tarotHouseTopInfoZodiacEl, "zodiac"], [elements.tarotHouseTopInfoTrumpEl, "trump"], [elements.tarotHouseTopInfoPathEl, "path"] ].forEach(([checkbox, key]) => { if (!checkbox) { return; } checkbox.addEventListener("change", () => { state.houseTopInfoModes[key] = Boolean(checkbox.checked); renderHouseOfCards(elements); syncHouseControls(elements); }); }); if (elements.tarotHouseBottomCardsVisibleEl) { elements.tarotHouseBottomCardsVisibleEl.addEventListener("change", () => { state.houseBottomCardsVisible = Boolean(elements.tarotHouseBottomCardsVisibleEl.checked); renderHouseOfCards(elements); syncHouseControls(elements); }); } [ [elements.tarotHouseBottomInfoZodiacEl, "zodiac"], [elements.tarotHouseBottomInfoDecanEl, "decan"], [elements.tarotHouseBottomInfoMonthEl, "month"], [elements.tarotHouseBottomInfoRulerEl, "ruler"], [elements.tarotHouseBottomInfoDateEl, "date"] ].forEach(([checkbox, key]) => { if (!checkbox) { return; } checkbox.addEventListener("change", () => { state.houseBottomInfoModes[key] = Boolean(checkbox.checked); renderHouseOfCards(elements); syncHouseControls(elements); }); }); if (elements.tarotHouseExportEl) { elements.tarotHouseExportEl.addEventListener("click", () => { exportHouseOfCards(elements, "png"); }); } if (elements.tarotHouseExportWebpEl) { elements.tarotHouseExportWebpEl.addEventListener("click", () => { exportHouseOfCards(elements, "webp"); }); } if (elements.tarotDetailImageEl) { elements.tarotDetailImageEl.addEventListener("click", () => { if (elements.tarotDetailImageEl.style.display === "none" || !state.selectedCardId) { return; } const request = buildLightboxCardRequestById(state.selectedCardId); if (!request?.src) { return; } window.TarotUiLightbox?.open?.(request); }); } state.initialized = true; })(); try { await state.loadingPromise; } finally { state.loadingPromise = null; } } function selectCardByTrump(trumpNumber) { if (!state.initialized) return; const el = getElements(); const card = state.cards.find(c => c.arcana === "Major" && c.number === trumpNumber); if (!card) return; selectCardById(card.id, el); scrollCardIntoView(card.id, el); } function selectCardByName(name) { if (!state.initialized) return; const el = getElements(); const needle = normalizeTarotCardLookupName(name); const card = state.cards.find((entry) => normalizeTarotCardLookupName(entry.name) === needle); if (!card) return; selectCardById(card.id, el); scrollCardIntoView(card.id, el); } window.TarotSectionUi = { ensureTarotSection, selectCardByTrump, selectCardByName, getCards: () => state.cards }; })();