2026-03-07 01:09:00 -08:00
( function () {
2026-03-08 22:24:34 -07:00
const dataService = window . TarotDataService || {};
2026-03-08 06:22:27 -07:00
const {
resolveTarotCardImage ,
resolveTarotCardThumbnail ,
getTarotCardDisplayName ,
getTarotCardSearchAliases ,
getDeckOptions ,
getActiveDeck
} = window . TarotCardImages || {};
2026-03-07 05:17:50 -08:00
const tarotHouseUi = window . TarotHouseUi || {};
const tarotRelationsUi = window . TarotRelationsUi || {};
2026-03-07 13:38:13 -08:00
const tarotCardDerivations = window . TarotCardDerivations || {};
const tarotDetailUi = window . TarotDetailUi || {};
const tarotRelationDisplay = window . TarotRelationDisplay || {};
2026-03-07 01:09:00 -08:00
const state = {
initialized : false ,
cards : [],
filteredCards : [],
searchQuery : "" ,
selectedCardId : "" ,
2026-03-08 03:52:25 -07:00
houseFocusMode : false ,
houseTopCardsVisible : true ,
houseTopInfoModes : {
hebrew : true ,
planet : true ,
zodiac : true ,
trump : true ,
2026-04-01 12:31:56 -07:00
path : true ,
date : false
2026-03-08 03:52:25 -07:00
},
houseBottomCardsVisible : true ,
houseBottomInfoModes : {
zodiac : true ,
decan : true ,
month : true ,
ruler : true ,
date : false
},
houseExportInProgress : false ,
houseExportFormat : "png" ,
2026-04-01 12:31:56 -07:00
houseSettingsOpen : false ,
2026-03-07 01:09:00 -08:00
magickDataset : null ,
referenceData : null ,
monthRefsByCardId : new Map (),
2026-03-08 22:24:34 -07:00
courtCardByDecanId : new Map (),
loadingPromise : null
2026-03-07 01:09:00 -08:00
};
2026-05-28 18:19:13 -07:00
let detailNavigator = null ;
2026-03-07 01:09:00 -08:00
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"
}
};
2026-03-07 14:15:09 -08:00
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"
};
2026-03-07 01:09:00 -08:00
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 {
2026-03-12 21:01:32 -07:00
tarotSectionEl : document . getElementById ( "tarot-section" ),
tarotHouseSectionEl : document . getElementById ( "tarot-house-section" ),
2026-03-07 01:09:00 -08:00
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" ),
2026-05-28 18:19:13 -07:00
tarotDetailPrevEl : document . getElementById ( "tarot-detail-prev" ),
tarotDetailPositionEl : document . getElementById ( "tarot-detail-position" ),
tarotDetailNextEl : document . getElementById ( "tarot-detail-next" ),
2026-03-07 01:09:00 -08:00
tarotDetailSummaryEl : document . getElementById ( "tarot-detail-summary" ),
tarotDetailUprightEl : document . getElementById ( "tarot-detail-upright" ),
tarotDetailReversedEl : document . getElementById ( "tarot-detail-reversed" ),
2026-05-28 18:19:13 -07:00
tarotMetaDeckGalleryCardEl : document . getElementById ( "tarot-meta-deck-gallery-card" ),
tarotDetailDeckGalleryEl : document . getElementById ( "tarot-detail-deck-gallery" ),
2026-03-07 01:09:00 -08:00
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" ),
2026-03-07 16:13:58 -08:00
tarotMetaIChingCardEl : document . getElementById ( "tarot-meta-iching-card" ),
2026-03-07 01:09:00 -08:00
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" ),
2026-03-07 16:13:58 -08:00
tarotDetailIChingEl : document . getElementById ( "tarot-detail-iching" ),
2026-03-07 01:09:00 -08:00
tarotDetailCalendarEl : document . getElementById ( "tarot-detail-calendar" ),
tarotKabPathEl : document . getElementById ( "tarot-kab-path" ),
2026-03-08 03:52:25 -07:00
tarotHouseOfCardsEl : document . getElementById ( "tarot-house-of-cards" ),
tarotBrowseViewEl : document . getElementById ( "tarot-browse-view" ),
2026-03-12 21:01:32 -07:00
tarotHouseViewEl : document . getElementById ( "tarot-house-view" ),
2026-04-01 12:31:56 -07:00
tarotHouseSettingsToggleEl : document . getElementById ( "tarot-house-settings-toggle" ),
tarotHouseSettingsPanelEl : document . getElementById ( "tarot-house-settings-panel" ),
2026-03-08 03:52:25 -07:00
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" ),
2026-04-01 12:31:56 -07:00
tarotHouseTopInfoDateEl : document . getElementById ( "tarot-house-top-info-date" ),
2026-03-08 03:52:25 -07:00
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" ),
tarotHouseExportWebpEl : document . getElementById ( "tarot-house-export-webp" )
2026-03-07 01:09:00 -08:00
};
}
2026-03-08 03:52:25 -07:00
function setHouseBottomInfoCheckboxState ( checkbox , enabled ) {
if ( ! checkbox ) {
return ;
}
checkbox . checked = Boolean ( enabled );
checkbox . disabled = Boolean ( state . houseExportInProgress );
}
2026-03-07 01:09:00 -08:00
function normalizeRelationId ( value ) {
return String ( value || "" )
. trim ()
. toLowerCase ()
. replace ( /[^a-z0-9]+/g , "-" )
. replace ( /(^-|-$)/g , "" );
}
2026-03-07 13:38:13 -08:00
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 ,
2026-03-08 05:40:53 -07:00
resolveTarotCardThumbnail ,
2026-05-28 18:19:13 -07:00
getDeckVariantsForCard ,
openDeckVariantLightbox ,
2026-03-07 13:38:13 -08:00
getDisplayCardName ,
buildTypeLabel ,
clearChildren ,
normalizeRelationObject ,
buildElementRelationsForCard ,
buildTetragrammatonRelationsForCard ,
buildSmallCardRulershipRelation ,
buildSmallCardCourtLinkRelations ,
buildCubeRelationsForCard ,
2026-03-07 16:13:58 -08:00
buildIChingRelationsForCard ,
2026-03-07 13:38:13 -08:00
parseMonthDayToken ,
createRelationListItem ,
findSephirahForMinorCard
});
2026-03-07 01:09:00 -08:00
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 = []) {
2026-03-07 13:38:13 -08:00
return tarotCardDerivationsUi . buildElementRelationsForCard ( card , baseElementRelations );
2026-03-07 01:09:00 -08:00
}
function buildTetragrammatonRelationsForCard ( card ) {
2026-03-07 13:38:13 -08:00
return tarotCardDerivationsUi . buildTetragrammatonRelationsForCard ( card );
2026-03-07 01:09:00 -08:00
}
function buildSmallCardRulershipRelation ( card ) {
2026-03-07 13:38:13 -08:00
return tarotCardDerivationsUi . buildSmallCardRulershipRelation ( card );
2026-03-07 01:09:00 -08:00
}
function buildCourtCardByDecanId ( cards ) {
2026-03-07 05:17:50 -08:00
if ( typeof tarotRelationsUi . buildCourtCardByDecanId !== "function" ) {
return new Map ();
}
return tarotRelationsUi . buildCourtCardByDecanId ( cards );
2026-03-07 01:09:00 -08:00
}
function buildSmallCardCourtLinkRelations ( card , relations ) {
2026-03-07 05:17:50 -08:00
if ( typeof tarotRelationsUi . buildSmallCardCourtLinkRelations !== "function" ) {
2026-03-07 01:09:00 -08:00
return [];
}
2026-03-07 05:17:50 -08:00
return tarotRelationsUi . buildSmallCardCourtLinkRelations ( card , relations , state . courtCardByDecanId );
2026-03-07 01:09:00 -08:00
}
function parseMonthDayToken ( value ) {
2026-03-07 05:17:50 -08:00
if ( typeof tarotRelationsUi . parseMonthDayToken !== "function" ) {
2026-03-07 01:09:00 -08:00
return null ;
}
2026-03-07 05:17:50 -08:00
return tarotRelationsUi . parseMonthDayToken ( value );
2026-03-07 01:09:00 -08:00
}
function buildMonthReferencesByCard ( referenceData , cards ) {
2026-03-07 05:17:50 -08:00
if ( typeof tarotRelationsUi . buildMonthReferencesByCard !== "function" ) {
return new Map ();
2026-03-07 01:09:00 -08:00
}
2026-03-07 05:17:50 -08:00
return tarotRelationsUi . buildMonthReferencesByCard ( referenceData , cards );
2026-03-07 01:09:00 -08:00
}
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 );
2026-05-28 18:19:13 -07:00
} else {
syncDetailNavigation ( elements );
2026-03-07 01:09:00 -08:00
}
return ;
}
updateListSelection ( elements );
2026-05-28 18:19:13 -07:00
syncDetailNavigation ( elements );
2026-03-07 01:09:00 -08:00
}
function clearChildren ( element ) {
if ( element ) {
element . replaceChildren ();
}
}
function updateHouseSelection ( elements ) {
2026-03-07 05:17:50 -08:00
tarotHouseUi . updateSelection ? .( elements );
2026-03-07 01:09:00 -08:00
}
function renderHouseOfCards ( elements ) {
2026-03-07 05:17:50 -08:00
tarotHouseUi . render ? .( elements );
2026-03-07 01:09:00 -08:00
}
2026-03-08 03:52:25 -07:00
function syncHouseControls ( elements ) {
2026-03-12 21:01:32 -07:00
if ( elements ? . tarotHouseViewEl ) {
elements . tarotHouseViewEl . classList . toggle ( "is-house-focus" , Boolean ( state . houseFocusMode ));
2026-03-08 03:52:25 -07:00
}
2026-04-01 12:31:56 -07:00
if ( elements ? . tarotHouseSettingsToggleEl ) {
elements . tarotHouseSettingsToggleEl . setAttribute ( "aria-expanded" , state . houseSettingsOpen ? "true" : "false" );
elements . tarotHouseSettingsToggleEl . textContent = state . houseSettingsOpen ? "Hide Settings" : "Settings" ;
}
if ( elements ? . tarotHouseSettingsPanelEl ) {
elements . tarotHouseSettingsPanelEl . hidden = ! state . houseSettingsOpen ;
}
2026-03-08 03:52:25 -07:00
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 );
2026-04-01 12:31:56 -07:00
setHouseBottomInfoCheckboxState ( elements ? . tarotHouseTopInfoDateEl , state . houseTopInfoModes . date );
2026-03-08 03:52:25 -07:00
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 ? . 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." ;
}
}
}
2026-04-01 16:08:52 -07:00
function refreshHouseUi () {
if ( ! state . initialized ) {
return ;
}
const elements = getElements ();
renderHouseOfCards ( elements );
syncHouseControls ( elements );
}
function setHouseTopCardsVisible ( value ) {
state . houseTopCardsVisible = Boolean ( value );
refreshHouseUi ();
}
function setHouseTopInfoMode ( mode , value ) {
const key = String ( mode || "" ). trim ();
if ( ! key || ! Object . prototype . hasOwnProperty . call ( state . houseTopInfoModes , key )) {
return ;
}
state . houseTopInfoModes [ key ] = Boolean ( value );
refreshHouseUi ();
}
function setHouseBottomCardsVisible ( value ) {
state . houseBottomCardsVisible = Boolean ( value );
refreshHouseUi ();
}
function setHouseBottomInfoMode ( mode , value ) {
const key = String ( mode || "" ). trim ();
if ( ! key || ! Object . prototype . hasOwnProperty . call ( state . houseBottomInfoModes , key )) {
return ;
}
state . houseBottomInfoModes [ key ] = Boolean ( value );
refreshHouseUi ();
}
2026-03-08 03:52:25 -07:00
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 );
}
}
2026-03-07 01:09:00 -08:00
function buildTypeLabel ( card ) {
2026-03-07 13:38:13 -08:00
return tarotCardDerivationsUi . buildTypeLabel ( card );
2026-03-07 01:09:00 -08:00
}
function findSephirahForMinorCard ( card , kabTree ) {
2026-03-07 13:38:13 -08:00
return tarotCardDerivationsUi . findSephirahForMinorCard ( card , kabTree );
2026-03-07 01:09:00 -08:00
}
function formatRelation ( relation ) {
2026-03-07 13:38:13 -08:00
return tarotRelationDisplayUi . formatRelation ( relation );
2026-03-07 01:09:00 -08:00
}
function relationKey ( relation , index ) {
2026-03-07 13:38:13 -08:00
return tarotRelationDisplayUi . relationKey ( relation , index );
2026-03-07 01:09:00 -08:00
}
function normalizeRelationObject ( relation , index ) {
2026-03-07 13:38:13 -08:00
return tarotRelationDisplayUi . normalizeRelationObject ( relation , index );
2026-03-07 01:09:00 -08:00
}
function formatRelationDataLines ( relation ) {
2026-03-07 13:38:13 -08:00
return tarotRelationDisplayUi . formatRelationDataLines ( relation );
2026-03-07 01:09:00 -08:00
}
function buildCubeRelationsForCard ( card ) {
2026-03-07 05:17:50 -08:00
if ( typeof tarotRelationsUi . buildCubeRelationsForCard !== "function" ) {
return [];
}
return tarotRelationsUi . buildCubeRelationsForCard ( card , state . magickDataset );
2026-03-07 01:09:00 -08:00
}
2026-03-07 16:13:58 -08:00
function buildIChingRelationsForCard ( card ) {
if ( typeof tarotRelationsUi . buildIChingRelationsForCard !== "function" ) {
return [];
}
return tarotRelationsUi . buildIChingRelationsForCard ( card , state . referenceData );
}
2026-03-07 01:09:00 -08:00
// Returns nav dispatch config for relations that have a corresponding section,
// null for informational-only relations.
function getRelationNavTarget ( relation ) {
2026-03-07 13:38:13 -08:00
return tarotRelationDisplayUi . getRelationNavTarget ( relation );
2026-03-07 01:09:00 -08:00
}
function createRelationListItem ( relation ) {
2026-03-07 13:38:13 -08:00
return tarotRelationDisplayUi . createRelationListItem ( relation );
2026-03-07 01:09:00 -08:00
}
function renderStaticRelationGroup ( targetEl , cardEl , relations ) {
2026-03-07 13:38:13 -08:00
tarotDetailRenderer . renderStaticRelationGroup ( targetEl , cardEl , relations );
2026-03-07 01:09:00 -08:00
}
function renderDetail ( card , elements ) {
2026-03-07 13:38:13 -08:00
tarotDetailRenderer . renderDetail ( card , elements );
2026-03-07 01:09:00 -08:00
}
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" );
});
}
2026-05-28 18:19:13 -07:00
function getCardSequenceState () {
const total = state . filteredCards . length ;
const currentIndex = state . filteredCards . findIndex (( card ) => card . id === state . selectedCardId );
return {
total ,
currentIndex ,
previousId : currentIndex > 0 ? state . filteredCards [ currentIndex - 1 ]. id : "" ,
nextId : currentIndex >= 0 && currentIndex < total - 1 ? state . filteredCards [ currentIndex + 1 ]. id : ""
};
}
function getDetailNavigator () {
if ( detailNavigator || typeof window . TarotSequenceNav ? . createSequenceNavigator !== "function" ) {
return detailNavigator ;
}
detailNavigator = window . TarotSequenceNav . createSequenceNavigator ({
getElements ,
isActive : ( elements ) => Boolean ( elements ? . tarotSectionEl && elements . tarotSectionEl . hidden === false ),
getSequenceState : getCardSequenceState ,
getPrevButton : ( elements ) => elements ? . tarotDetailPrevEl ,
getNextButton : ( elements ) => elements ? . tarotDetailNextEl ,
getPositionEl : ( elements ) => elements ? . tarotDetailPositionEl ,
formatPositionText : ({ total , currentIndex }) => {
if ( total > 0 && currentIndex >= 0 ) {
const suffix = state . searchQuery ? " shown" : "" ;
return ` ${ currentIndex + 1 } of ${ total }${ suffix } ` ;
}
return total > 0 ? ` ${ total } cards` : "No cards" ;
},
selectTarget : ( targetId , elements ) => {
selectCardById ( targetId , elements );
return true ;
},
afterSelect : ( targetId , elements ) => {
scrollCardIntoView ( targetId , elements );
}
});
return detailNavigator ;
}
function syncDetailNavigation ( elements ) {
getDetailNavigator () ? . sync ( elements );
}
function selectAdjacentCard ( offset , elements = getElements ()) {
return getDetailNavigator () ? . step ( offset , elements ) === true ;
}
function bindKeyboardNavigation ( elements ) {
getDetailNavigator () ? . bind ( elements );
}
2026-03-07 01:09:00 -08:00
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 );
2026-05-28 18:19:13 -07:00
syncDetailNavigation ( elements );
2026-03-07 01:09:00 -08:00
}
2026-03-08 03:52:25 -07:00
function scrollCardIntoView ( cardIdToReveal , elements ) {
elements ? . tarotCardListEl
? . querySelector ( `[data-card-id=" ${ cardIdToReveal } "]` )
? . scrollIntoView ({ block : "nearest" });
}
2026-03-08 06:22:27 -07:00
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 ());
}
2026-05-28 18:19:13 -07:00
function getDeckVariantsForCard ( card ) {
if ( ! card ) {
return [];
}
const trumpNumber = Number . isFinite ( Number ( card ? . number )) ? Number ( card . number ) : undefined ;
const activeDeckId = String ( getActiveDeck ? .() || "" ). trim (). toLowerCase ();
return getRegisteredDeckList ()
. map (( deck ) => {
const deckId = String ( deck ? . id || "" ). trim (). toLowerCase ();
if ( ! deckId ) {
return null ;
}
const imageOptions = { deckId , trumpNumber };
const src = ( typeof resolveTarotCardThumbnail === "function"
? resolveTarotCardThumbnail ( card . name , imageOptions )
: "" ) || ( typeof resolveTarotCardImage === "function"
? resolveTarotCardImage ( card . name , imageOptions )
: "" );
if ( ! src ) {
return null ;
}
const displayName = ( typeof getTarotCardDisplayName === "function"
? String ( getTarotCardDisplayName ( card . name , imageOptions ) || "" ). trim ()
: "" ) || getDisplayCardName ( card ) || card . name ;
return {
deckId ,
label : String ( deck ? . label || deckId ). trim () || deckId ,
src ,
displayName ,
isActive : deckId === activeDeckId
};
})
. filter ( Boolean );
}
2026-03-08 06:22:27 -07:00
function buildDeckLightboxCardRequest ( cardIdToResolve , deckIdToResolve = "" ) {
2026-03-08 03:52:25 -07:00
const card = state . cards . find (( entry ) => entry . id === cardIdToResolve );
if ( ! card ) {
return null ;
}
2026-03-08 06:22:27 -07:00
const resolvedDeckId = String ( deckIdToResolve || getActiveDeck ? .() || "" ). trim ();
const trumpNumber = Number . isFinite ( Number ( card ? . number )) ? Number ( card . number ) : undefined ;
const deckOptions = resolvedDeckId ? { deckId : resolvedDeckId , trumpNumber } : { trumpNumber };
2026-03-08 03:52:25 -07:00
const src = typeof resolveTarotCardImage === "function"
2026-03-08 06:22:27 -07:00
? resolveTarotCardImage ( card . name , deckOptions )
2026-03-08 03:52:25 -07:00
: "" ;
2026-04-22 00:16:20 -07:00
const previewSrc = typeof resolveTarotCardThumbnail === "function"
? ( resolveTarotCardThumbnail ( card . name , deckOptions ) || src )
: src ;
2026-03-08 06:22:27 -07:00
const deckMeta = resolvedDeckId ? getRegisteredDeckOptionMap (). get ( resolvedDeckId ) : null ;
const label = ( typeof getTarotCardDisplayName === "function"
? getTarotCardDisplayName ( card . name , deckOptions )
: "" ) || getDisplayCardName ( card ) || card . name || "Tarot card enlarged image" ;
2026-03-08 03:52:25 -07:00
return {
src ,
2026-04-22 00:16:20 -07:00
previewSrc ,
2026-03-08 03:52:25 -07:00
altText : label ,
label ,
cardId : card . id ,
2026-03-08 06:22:27 -07:00
deckId : resolvedDeckId ,
deckLabel : deckMeta ? . label || resolvedDeckId ,
2026-03-08 03:52:25 -07:00
compareDetails : tarotDetailRenderer . buildCompareDetails ? .( card ) || []
};
}
2026-05-28 18:19:13 -07:00
function buildLightboxCardRequestById ( cardIdToResolve , deckIdToResolve = "" ) {
const request = buildDeckLightboxCardRequest ( cardIdToResolve , deckIdToResolve || getActiveDeck ? .() || "" );
2026-03-08 06:22:27 -07:00
if ( ! request ? . src ) {
return null ;
}
return request ;
}
2026-05-28 18:19:13 -07:00
function getDefaultLightboxSequenceIds ( cardIdToOpen = "" ) {
const normalizedCardId = String ( cardIdToOpen || "" ). trim ();
const filteredIds = state . filteredCards
. map (( card ) => String ( card ? . id || "" ). trim ())
. filter ( Boolean );
if ( normalizedCardId && filteredIds . includes ( normalizedCardId )) {
return filteredIds ;
}
return state . cards
. map (( card ) => String ( card ? . id || "" ). trim ())
. filter ( Boolean );
}
function getDeckVariantSequenceEntries ( cardIdToResolve ) {
const card = state . cards . find (( entry ) => entry . id === cardIdToResolve );
if ( ! card ) {
return [];
}
return getDeckVariantsForCard ( card )
. map (( variant ) => {
const deckId = String ( variant ? . deckId || "" ). trim (). toLowerCase ();
if ( ! deckId ) {
return null ;
}
return {
sequenceId : deckId ,
deckId
};
})
. filter ( Boolean );
}
2026-04-02 22:06:19 -07:00
function openCardLightboxById ( cardIdToOpen , options = {}) {
const normalizedCardId = String ( cardIdToOpen || "" ). trim ();
if ( ! normalizedCardId ) {
return ;
}
2026-05-28 18:19:13 -07:00
const requestedDeckId = String ( options ? . deckId || getActiveDeck ? .() || "" ). trim ();
const primaryCardRequest = buildLightboxCardRequestById ( normalizedCardId , requestedDeckId );
2026-04-02 22:06:19 -07:00
if ( ! primaryCardRequest ? . src ) {
return ;
}
2026-05-28 18:19:13 -07:00
const activeDeckId = String ( primaryCardRequest . deckId || requestedDeckId || getActiveDeck ? .() || "" ). trim ();
2026-04-02 22:06:19 -07:00
const availableCompareDecks = getRegisteredDeckList (). filter (( deck ) => deck . id && deck . id !== activeDeckId );
2026-05-28 18:19:13 -07:00
const requestedSequenceIds = Array . isArray ( options ? . sequenceIds )
? options . sequenceIds
. map (( sequenceId ) => String ( sequenceId || "" ). trim ())
. filter ( Boolean )
: [];
const sequenceIds = requestedSequenceIds . length > 0
? requestedSequenceIds
: getDefaultLightboxSequenceIds ( normalizedCardId );
const resolveCardById = typeof options ? . resolveCardById === "function"
? options . resolveCardById
: ( nextCardId ) => buildLightboxCardRequestById ( nextCardId , activeDeckId );
2026-04-02 22:06:19 -07:00
const onSelectCardId = typeof options ? . onSelectCardId === "function"
? options . onSelectCardId
: ( nextCardId ) => {
const latestElements = getElements ();
selectCardById ( nextCardId , latestElements );
scrollCardIntoView ( nextCardId , latestElements );
};
window . TarotUiLightbox ? . open ? .({
src : primaryCardRequest . src ,
altText : primaryCardRequest . altText ,
label : primaryCardRequest . label ,
cardId : primaryCardRequest . cardId ,
2026-05-28 18:19:13 -07:00
sequenceId : String ( options ? . sequenceId || primaryCardRequest . sequenceId || normalizedCardId ). trim (),
2026-04-02 22:06:19 -07:00
deckId : primaryCardRequest . deckId || activeDeckId ,
deckLabel : primaryCardRequest . deckLabel || "" ,
compareDetails : primaryCardRequest . compareDetails || [],
allowOverlayCompare : true ,
allowDeckCompare : true ,
activeDeckId ,
activeDeckLabel : primaryCardRequest . deckLabel || "" ,
availableCompareDecks ,
maxCompareDecks : 2 ,
2026-05-28 18:19:13 -07:00
sequenceIds ,
resolveCardById ,
2026-04-02 22:06:19 -07:00
resolveDeckCardById : buildDeckLightboxCardRequest ,
onSelectCardId
});
}
2026-05-28 18:19:13 -07:00
function openDeckVariantLightbox ( cardIdToOpen , deckIdToOpen = "" ) {
const normalizedCardId = String ( cardIdToOpen || "" ). trim ();
if ( ! normalizedCardId ) {
return ;
}
const variantEntries = getDeckVariantSequenceEntries ( normalizedCardId );
if ( variantEntries . length < 1 ) {
openCardLightboxById ( normalizedCardId , { deckId : deckIdToOpen });
return ;
}
const requestedDeckId = String ( deckIdToOpen || getActiveDeck ? .() || "" ). trim (). toLowerCase ();
const activeVariant = variantEntries . find (( variant ) => variant . deckId === requestedDeckId ) || variantEntries [ 0 ];
if ( ! activeVariant ? . deckId ) {
openCardLightboxById ( normalizedCardId );
return ;
}
const primaryCardRequest = buildLightboxCardRequestById ( normalizedCardId , activeVariant . deckId );
if ( ! primaryCardRequest ? . src ) {
return ;
}
const availableCompareDecks = getRegisteredDeckList (). filter (( deck ) => deck . id && deck . id !== activeVariant . deckId );
window . TarotUiLightbox ? . open ? .({
... primaryCardRequest ,
deckId : activeVariant . deckId ,
sequenceId : activeVariant . sequenceId ,
activeDeckId : activeVariant . deckId ,
activeDeckLabel : primaryCardRequest . deckLabel || "" ,
availableCompareDecks ,
maxCompareDecks : 2 ,
allowOverlayCompare : true ,
allowDeckCompare : true ,
sequenceIds : variantEntries . map (( variant ) => variant . sequenceId ),
resolveDeckCardById : buildDeckLightboxCardRequest ,
resolveCardById : ( sequenceId ) => {
const normalizedSequenceId = String ( sequenceId || "" ). trim (). toLowerCase ();
const matchingVariant = variantEntries . find (( variant ) => variant . sequenceId === normalizedSequenceId );
if ( ! matchingVariant ? . deckId ) {
return null ;
}
const request = buildLightboxCardRequestById ( normalizedCardId , matchingVariant . deckId );
return request
? {
... request ,
sequenceId : matchingVariant . sequenceId ,
deckId : matchingVariant . deckId
}
: null ;
},
onSelectCardId : () => {
}
});
}
2026-03-07 01:09:00 -08:00
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` ;
}
}
2026-03-08 22:24:34 -07:00
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 ) {
2026-03-07 01:09:00 -08:00
state . referenceData = referenceData || state . referenceData ;
if ( magickDataset ) {
state . magickDataset = magickDataset ;
}
2026-03-07 05:17:50 -08:00
tarotHouseUi . init ? .({
resolveTarotCardImage ,
2026-03-08 05:40:53 -07:00
resolveTarotCardThumbnail ,
2026-03-28 17:31:45 -07:00
getActiveDeck ,
2026-03-07 05:17:50 -08:00
getDisplayCardName ,
clearChildren ,
normalizeTarotCardLookupName ,
selectCardById ,
2026-03-08 03:52:25 -07:00
openCardLightbox : ( src , altText , options = {}) => {
const cardId = String ( options ? . cardId || "" ). trim ();
2026-04-02 22:06:19 -07:00
if ( cardId ) {
openCardLightboxById ( cardId );
return ;
}
2026-03-08 03:52:25 -07:00
window . TarotUiLightbox ? . open ? .({
2026-04-02 22:06:19 -07:00
src ,
altText : altText || "Tarot card enlarged image" ,
label : altText || "Tarot card enlarged image"
2026-03-08 03:52:25 -07:00
});
},
2026-03-12 21:01:32 -07:00
shouldOpenCardLightboxOnSelect : ( latestElements ) => Boolean (
latestElements ? . tarotHouseSectionEl instanceof HTMLElement
&& latestElements . tarotHouseSectionEl . hidden === false
),
2026-03-08 03:52:25 -07:00
isHouseFocusMode : () => state . houseFocusMode ,
2026-03-07 05:17:50 -08:00
getCards : () => state . cards ,
2026-03-08 03:52:25 -07:00
getSelectedCardId : () => state . selectedCardId ,
getHouseTopCardsVisible : () => state . houseTopCardsVisible ,
getHouseTopInfoModes : () => ({ ... state . houseTopInfoModes }),
2026-04-01 12:31:56 -07:00
getMagickDataset : () => state . magickDataset ,
2026-03-08 03:52:25 -07:00
getHouseBottomCardsVisible : () => state . houseBottomCardsVisible ,
getHouseBottomInfoModes : () => ({ ... state . houseBottomInfoModes })
2026-03-07 05:17:50 -08:00
});
2026-03-07 01:09:00 -08:00
const elements = getElements ();
if ( state . initialized ) {
state . monthRefsByCardId = buildMonthReferencesByCard ( referenceData , state . cards );
state . courtCardByDecanId = buildCourtCardByDecanId ( state . cards );
renderHouseOfCards ( elements );
2026-03-08 03:52:25 -07:00
syncHouseControls ( elements );
2026-03-07 01:09:00 -08:00
if ( state . selectedCardId ) {
const selected = state . cards . find (( card ) => card . id === state . selectedCardId );
if ( selected ) {
renderDetail ( selected , elements );
2026-05-28 18:19:13 -07:00
syncDetailNavigation ( elements );
2026-03-07 01:09:00 -08:00
}
}
return ;
}
if ( ! elements . tarotCardListEl || ! elements . tarotDetailNameEl ) {
return ;
}
2026-03-08 22:24:34 -07:00
if ( state . loadingPromise ) {
await state . loadingPromise ;
2026-03-07 01:09:00 -08:00
return ;
}
2026-03-08 22:24:34 -07:00
state . loadingPromise = ( async () => {
const cards = await loadCards ( referenceData , magickDataset );
2026-03-07 01:09:00 -08:00
2026-03-08 22:24:34 -07:00
state . cards = cards ;
state . monthRefsByCardId = buildMonthReferencesByCard ( referenceData , cards );
state . courtCardByDecanId = buildCourtCardByDecanId ( cards );
state . filteredCards = [... cards ];
renderList ( elements );
renderHouseOfCards ( elements );
syncHouseControls ( elements );
2026-03-07 01:09:00 -08:00
2026-03-08 22:24:34 -07:00
if ( cards . length > 0 ) {
selectCardById ( cards [ 0 ]. id , elements );
2026-03-07 01:09:00 -08:00
}
2026-03-08 22:24:34 -07:00
elements . tarotCardListEl . addEventListener ( "click" , ( event ) => {
const target = event . target ;
if ( ! ( target instanceof Node )) {
2026-03-07 01:09:00 -08:00
return ;
}
2026-03-08 05:40:53 -07:00
2026-03-08 22:24:34 -07:00
const button = target instanceof Element
? target . closest ( ".tarot-list-item" )
: null ;
if ( ! ( button instanceof HTMLButtonElement )) {
2026-03-08 05:40:53 -07:00
return ;
}
2026-03-08 22:24:34 -07:00
const selectedId = button . dataset . cardId ;
if ( ! selectedId ) {
return ;
}
2026-03-07 01:09:00 -08:00
2026-03-08 22:24:34 -07:00
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 ();
});
}
2026-05-28 18:19:13 -07:00
bindKeyboardNavigation ( elements );
2026-03-08 22:24:34 -07:00
if ( elements . tarotHouseTopCardsVisibleEl ) {
elements . tarotHouseTopCardsVisibleEl . addEventListener ( "change" , () => {
state . houseTopCardsVisible = Boolean ( elements . tarotHouseTopCardsVisibleEl . checked );
renderHouseOfCards ( elements );
syncHouseControls ( elements );
});
}
2026-04-01 12:31:56 -07:00
if ( elements . tarotHouseSettingsToggleEl ) {
elements . tarotHouseSettingsToggleEl . addEventListener ( "click" , ( event ) => {
event . stopPropagation ();
state . houseSettingsOpen = ! state . houseSettingsOpen ;
syncHouseControls ( elements );
});
}
if ( elements . tarotHouseSettingsPanelEl ) {
elements . tarotHouseSettingsPanelEl . addEventListener ( "click" , ( event ) => {
event . stopPropagation ();
});
}
document . addEventListener ( "click" , ( event ) => {
if ( ! state . houseSettingsOpen ) {
return ;
}
const target = event . target ;
if ( ! ( target instanceof Node )) {
return ;
}
const settingsPanelEl = elements . tarotHouseSettingsPanelEl ;
const settingsToggleEl = elements . tarotHouseSettingsToggleEl ;
if ( settingsPanelEl ? . contains ( target ) || settingsToggleEl ? . contains ( target )) {
return ;
}
state . houseSettingsOpen = false ;
syncHouseControls ( elements );
});
2026-03-08 22:24:34 -07:00
[
[ elements . tarotHouseTopInfoHebrewEl , "hebrew" ],
[ elements . tarotHouseTopInfoPlanetEl , "planet" ],
[ elements . tarotHouseTopInfoZodiacEl , "zodiac" ],
[ elements . tarotHouseTopInfoTrumpEl , "trump" ],
2026-04-01 12:31:56 -07:00
[ elements . tarotHouseTopInfoPathEl , "path" ],
[ elements . tarotHouseTopInfoDateEl , "date" ]
2026-03-08 22:24:34 -07:00
]. 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 . tarotHouseExportWebpEl ) {
elements . tarotHouseExportWebpEl . addEventListener ( "click" , () => {
exportHouseOfCards ( elements , "webp" );
});
}
if ( elements . tarotDetailImageEl ) {
elements . tarotDetailImageEl . addEventListener ( "click" , () => {
if ( elements . tarotDetailImageEl . style . display === "none" || ! state . selectedCardId ) {
return ;
}
2026-05-28 18:19:13 -07:00
openCardLightboxById ( state . selectedCardId );
2026-03-08 22:24:34 -07:00
});
}
state . initialized = true ;
})();
try {
await state . loadingPromise ;
} finally {
state . loadingPromise = null ;
}
2026-03-07 01:09:00 -08:00
}
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 );
2026-03-08 03:52:25 -07:00
scrollCardIntoView ( card . id , el );
2026-03-07 01:09:00 -08:00
}
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 );
2026-03-08 03:52:25 -07:00
scrollCardIntoView ( card . id , el );
2026-03-07 01:09:00 -08:00
}
window . TarotSectionUi = {
ensureTarotSection ,
selectCardByTrump ,
selectCardByName ,
2026-04-02 22:06:19 -07:00
openCardLightboxById ,
2026-04-01 16:08:52 -07:00
getCards : () => state . cards ,
getHouseTopCardsVisible : () => state . houseTopCardsVisible ,
getHouseTopInfoModes : () => ({ ... state . houseTopInfoModes }),
getHouseBottomCardsVisible : () => state . houseBottomCardsVisible ,
getHouseBottomInfoModes : () => ({ ... state . houseBottomInfoModes }),
setHouseTopCardsVisible ,
setHouseTopInfoMode ,
setHouseBottomCardsVisible ,
setHouseBottomInfoMode
2026-03-07 01:09:00 -08:00
};
})();