(function () { "use strict"; function getCalendarMonthLinksForNumber(context, value) { const { getReferenceData, normalizeNumberValue, computeDigitalRoot } = context; const referenceData = getReferenceData(); const normalized = normalizeNumberValue(value); const calendarGroups = [ { calendarId: "gregorian", calendarLabel: "Gregorian", months: Array.isArray(referenceData?.calendarMonths) ? referenceData.calendarMonths : [] }, { calendarId: "hebrew", calendarLabel: "Hebrew", months: Array.isArray(referenceData?.hebrewCalendar?.months) ? referenceData.hebrewCalendar.months : [] }, { calendarId: "islamic", calendarLabel: "Islamic", months: Array.isArray(referenceData?.islamicCalendar?.months) ? referenceData.islamicCalendar.months : [] }, { calendarId: "wheel-of-year", calendarLabel: "Wheel of the Year", months: Array.isArray(referenceData?.wheelOfYear?.months) ? referenceData.wheelOfYear.months : [] } ]; const links = []; calendarGroups.forEach((group) => { group.months.forEach((month) => { const monthOrder = Number(month?.order); const normalizedOrder = Number.isFinite(monthOrder) ? Math.trunc(monthOrder) : null; const monthRoot = normalizedOrder != null ? computeDigitalRoot(normalizedOrder) : null; if (monthRoot !== normalized) { return; } links.push({ calendarId: group.calendarId, calendarLabel: group.calendarLabel, monthId: String(month.id || "").trim(), monthName: String(month.name || month.id || "Month").trim(), monthOrder: normalizedOrder }); }); }); return links.filter((link) => link.monthId); } function buildFallbackPlayingDeckEntries(context) { const { PLAYING_SUIT_SYMBOL, PLAYING_SUIT_LABEL, PLAYING_SUIT_TO_TAROT, PLAYING_RANKS, rankLabelToTarotMinorRank } = context; const entries = []; Object.keys(PLAYING_SUIT_SYMBOL).forEach((suit) => { PLAYING_RANKS.forEach((rank) => { const tarotSuit = PLAYING_SUIT_TO_TAROT[suit]; const tarotRank = rankLabelToTarotMinorRank(rank.rankLabel); entries.push({ id: `${rank.rank}${PLAYING_SUIT_SYMBOL[suit]}`, suit, suitLabel: PLAYING_SUIT_LABEL[suit], suitSymbol: PLAYING_SUIT_SYMBOL[suit], rank: rank.rank, rankLabel: rank.rankLabel, rankValue: rank.rankValue, tarotSuit, tarotCard: `${tarotRank} of ${tarotSuit}` }); }); }); return entries; } function getPlayingDeckEntries(context) { const { getMagickDataset, PLAYING_SUIT_SYMBOL, PLAYING_SUIT_LABEL, PLAYING_SUIT_TO_TAROT, rankLabelToTarotMinorRank } = context; const deckData = getMagickDataset()?.grouped?.["playing-cards-52"]; const rawEntries = Array.isArray(deckData) ? deckData : (Array.isArray(deckData?.entries) ? deckData.entries : []); if (!rawEntries.length) { return buildFallbackPlayingDeckEntries(context); } return rawEntries .map((entry) => { const suit = String(entry?.suit || "").trim().toLowerCase(); const rankLabel = String(entry?.rankLabel || "").trim(); const rank = String(entry?.rank || "").trim(); if (!suit || !rank) { return null; } const suitSymbol = String(entry?.suitSymbol || PLAYING_SUIT_SYMBOL[suit] || "").trim(); const tarotSuit = String(entry?.tarotSuit || PLAYING_SUIT_TO_TAROT[suit] || "").trim(); const tarotCard = String(entry?.tarotCard || "").trim(); const rankValueRaw = Number(entry?.rankValue); const rankValue = Number.isFinite(rankValueRaw) ? Math.trunc(rankValueRaw) : null; return { id: String(entry?.id || `${rank}${suitSymbol}`).trim(), suit, suitLabel: String(entry?.suitLabel || PLAYING_SUIT_LABEL[suit] || suit).trim(), suitSymbol, rank, rankLabel: rankLabel || rank, rankValue, tarotSuit, tarotCard: tarotCard || `${rankLabelToTarotMinorRank(rankLabel || rank)} of ${tarotSuit}` }; }) .filter(Boolean); } function findPlayingCardBySuitAndValue(entries, suit, value) { const normalizedSuit = String(suit || "").trim().toLowerCase(); const targetValue = Number(value); return entries.find((entry) => entry.suit === normalizedSuit && Number(entry.rankValue) === targetValue) || null; } function buildNumbersSpecialCardSlots(context, playingSuit) { const { PLAYING_SUIT_LABEL, PLAYING_SUIT_TO_TAROT, NUMBERS_SPECIAL_BASE_VALUES, numbersSpecialFlipState } = context; const suit = String(playingSuit || "hearts").trim().toLowerCase(); const selectedSuit = ["hearts", "diamonds", "clubs", "spades"].includes(suit) ? suit : "hearts"; const deckEntries = getPlayingDeckEntries(context); const cardEl = document.createElement("div"); cardEl.className = "numbers-detail-card numbers-special-card-section"; const headingEl = document.createElement("strong"); headingEl.textContent = "4 Card Arrangement"; const subEl = document.createElement("div"); subEl.className = "numbers-detail-text numbers-detail-text--muted"; subEl.textContent = `Click a card to flip to its opposite (${PLAYING_SUIT_LABEL[selectedSuit]} ↔ ${PLAYING_SUIT_TO_TAROT[selectedSuit]}).`; const boardEl = document.createElement("div"); boardEl.className = "numbers-special-board"; NUMBERS_SPECIAL_BASE_VALUES.forEach((baseValue) => { const oppositeValue = 9 - baseValue; const frontCard = findPlayingCardBySuitAndValue(deckEntries, selectedSuit, baseValue); const backCard = findPlayingCardBySuitAndValue(deckEntries, selectedSuit, oppositeValue); if (!frontCard || !backCard) { return; } const slotKey = `${selectedSuit}:${baseValue}`; const isFlipped = Boolean(numbersSpecialFlipState.get(slotKey)); const faceBtn = document.createElement("button"); faceBtn.type = "button"; faceBtn.className = `numbers-special-card${isFlipped ? " is-flipped" : ""}`; faceBtn.setAttribute("aria-pressed", isFlipped ? "true" : "false"); faceBtn.setAttribute("aria-label", `${frontCard.rankLabel} of ${frontCard.suitLabel}. Click to flip to ${backCard.rankLabel}.`); faceBtn.dataset.suit = selectedSuit; const innerEl = document.createElement("div"); innerEl.className = "numbers-special-card-inner"; const frontFaceEl = document.createElement("div"); frontFaceEl.className = "numbers-special-card-face numbers-special-card-face--front"; const frontRankEl = document.createElement("div"); frontRankEl.className = "numbers-special-card-rank"; frontRankEl.textContent = frontCard.rankLabel; const frontSuitEl = document.createElement("div"); frontSuitEl.className = "numbers-special-card-suit"; frontSuitEl.textContent = frontCard.suitSymbol; const frontMetaEl = document.createElement("div"); frontMetaEl.className = "numbers-special-card-meta"; frontMetaEl.textContent = frontCard.tarotCard; frontFaceEl.append(frontRankEl, frontSuitEl, frontMetaEl); const backFaceEl = document.createElement("div"); backFaceEl.className = "numbers-special-card-face numbers-special-card-face--back"; const backTagEl = document.createElement("div"); backTagEl.className = "numbers-special-card-tag"; backTagEl.textContent = "Opposite"; const backRankEl = document.createElement("div"); backRankEl.className = "numbers-special-card-rank"; backRankEl.textContent = backCard.rankLabel; const backSuitEl = document.createElement("div"); backSuitEl.className = "numbers-special-card-suit"; backSuitEl.textContent = backCard.suitSymbol; const backMetaEl = document.createElement("div"); backMetaEl.className = "numbers-special-card-meta"; backMetaEl.textContent = backCard.tarotCard; backFaceEl.append(backTagEl, backRankEl, backSuitEl, backMetaEl); innerEl.append(frontFaceEl, backFaceEl); faceBtn.append(innerEl); faceBtn.addEventListener("click", () => { const next = !Boolean(numbersSpecialFlipState.get(slotKey)); numbersSpecialFlipState.set(slotKey, next); faceBtn.classList.toggle("is-flipped", next); faceBtn.setAttribute("aria-pressed", next ? "true" : "false"); }); boardEl.appendChild(faceBtn); }); if (!boardEl.childElementCount) { const emptyEl = document.createElement("div"); emptyEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyEl.textContent = "No card slots available for this mapping yet."; boardEl.appendChild(emptyEl); } cardEl.append(headingEl, subEl, boardEl); return cardEl; } function renderNumbersSpecialPanel(context, value, entry) { const { specialPanelEl } = context.elements; if (!specialPanelEl) { return; } const playingSuit = entry?.associations?.playingSuit || "hearts"; const boardCardEl = buildNumbersSpecialCardSlots(context, playingSuit); specialPanelEl.replaceChildren(boardCardEl); } function parseTarotCardNumber(rawValue) { if (typeof rawValue === "number") { return Number.isFinite(rawValue) ? Math.trunc(rawValue) : null; } if (typeof rawValue === "string") { const trimmed = rawValue.trim(); if (!trimmed || !/^-?\d+$/.test(trimmed)) { return null; } return Number(trimmed); } return null; } function extractTarotCardNumericValue(context, card) { const { TAROT_RANK_NUMBER_MAP } = context; const directNumber = parseTarotCardNumber(card?.number); if (directNumber !== null) { return directNumber; } const rankKey = String(card?.rank || "").trim().toLowerCase(); if (Object.prototype.hasOwnProperty.call(TAROT_RANK_NUMBER_MAP, rankKey)) { return TAROT_RANK_NUMBER_MAP[rankKey]; } const numerologyRelation = Array.isArray(card?.relations) ? card.relations.find((relation) => String(relation?.type || "").trim().toLowerCase() === "numerology") : null; const relationValue = Number(numerologyRelation?.data?.value); if (Number.isFinite(relationValue)) { return Math.trunc(relationValue); } return null; } function getAlphabetPositionLinksForDigitalRoot(context, targetRoot) { const { getMagickDataset, computeDigitalRoot } = context; const alphabets = getMagickDataset()?.grouped?.alphabets; if (!alphabets || typeof alphabets !== "object") { return []; } const links = []; const addLink = (alphabetLabel, entry, buttonLabel, detail) => { const index = Number(entry?.index); if (!Number.isFinite(index)) { return; } const normalizedIndex = Math.trunc(index); if (computeDigitalRoot(normalizedIndex) !== targetRoot) { return; } links.push({ alphabet: alphabetLabel, index: normalizedIndex, label: buttonLabel, detail }); }; const toTitle = (value) => String(value || "") .trim() .replace(/[_-]+/g, " ") .replace(/\s+/g, " ") .toLowerCase() .replace(/\b([a-z])/g, (match, ch) => ch.toUpperCase()); const englishEntries = Array.isArray(alphabets.english) ? alphabets.english : []; englishEntries.forEach((entry) => { const letter = String(entry?.letter || "").trim(); if (!letter) { return; } addLink("English", entry, `${letter}`, { alphabet: "english", englishLetter: letter }); }); const greekEntries = Array.isArray(alphabets.greek) ? alphabets.greek : []; greekEntries.forEach((entry) => { const greekName = String(entry?.name || "").trim(); if (!greekName) { return; } const glyph = String(entry?.char || "").trim(); const displayName = String(entry?.displayName || toTitle(greekName)).trim(); addLink("Greek", entry, glyph ? `${displayName} - ${glyph}` : displayName, { alphabet: "greek", greekName }); }); const hebrewEntries = Array.isArray(alphabets.hebrew) ? alphabets.hebrew : []; hebrewEntries.forEach((entry) => { const hebrewLetterId = String(entry?.hebrewLetterId || "").trim(); if (!hebrewLetterId) { return; } const glyph = String(entry?.char || "").trim(); const name = String(entry?.name || hebrewLetterId).trim(); const displayName = toTitle(name); addLink("Hebrew", entry, glyph ? `${displayName} - ${glyph}` : displayName, { alphabet: "hebrew", hebrewLetterId }); }); const arabicEntries = Array.isArray(alphabets.arabic) ? alphabets.arabic : []; arabicEntries.forEach((entry) => { const arabicName = String(entry?.name || "").trim(); if (!arabicName) { return; } const glyph = String(entry?.char || "").trim(); const displayName = toTitle(arabicName); addLink("Arabic", entry, glyph ? `${displayName} - ${glyph}` : displayName, { alphabet: "arabic", arabicName }); }); const enochianEntries = Array.isArray(alphabets.enochian) ? alphabets.enochian : []; enochianEntries.forEach((entry) => { const enochianId = String(entry?.id || "").trim(); if (!enochianId) { return; } const title = String(entry?.title || enochianId).trim(); const displayName = toTitle(title); addLink("Enochian", entry, `${displayName}`, { alphabet: "enochian", enochianId }); }); return links.sort((left, right) => { if (left.index !== right.index) { return left.index - right.index; } const alphabetCompare = left.alphabet.localeCompare(right.alphabet); if (alphabetCompare !== 0) { return alphabetCompare; } return left.label.localeCompare(right.label); }); } function getTarotCardsForDigitalRoot(context, targetRoot, numberEntry = null) { const { getReferenceData, getMagickDataset, ensureTarotSection, computeDigitalRoot } = context; const referenceData = getReferenceData(); const magickDataset = getMagickDataset(); if (typeof ensureTarotSection === "function" && referenceData) { ensureTarotSection(referenceData, magickDataset); } const allCards = window.TarotSectionUi?.getCards?.() || []; const explicitTrumpNumbers = Array.isArray(numberEntry?.associations?.tarotTrumpNumbers) ? numberEntry.associations.tarotTrumpNumbers .map((value) => Number(value)) .filter((value) => Number.isFinite(value)) .map((value) => Math.trunc(value)) : []; const filteredCards = explicitTrumpNumbers.length ? allCards.filter((card) => { const numberValue = parseTarotCardNumber(card?.number); return card?.arcana === "Major" && numberValue !== null && explicitTrumpNumbers.includes(numberValue); }) : allCards.filter((card) => { const numberValue = extractTarotCardNumericValue(context, card); return numberValue !== null && computeDigitalRoot(numberValue) === targetRoot; }); return filteredCards.sort((left, right) => { const leftNumber = extractTarotCardNumericValue(context, left); const rightNumber = extractTarotCardNumericValue(context, right); if (leftNumber !== rightNumber) { return (leftNumber ?? 0) - (rightNumber ?? 0); } if (left?.arcana !== right?.arcana) { return left?.arcana === "Major" ? -1 : 1; } return String(left?.name || "").localeCompare(String(right?.name || "")); }); } function renderNumberDetail(context) { const { elements, getNumberEntryByValue, normalizeNumberValue, selectNumberEntry } = context; const { detailNameEl, detailTypeEl, detailSummaryEl, detailBodyEl } = elements; const entry = getNumberEntryByValue(context.value); if (!entry) { return; } const normalized = entry.value; const opposite = entry.opposite; const rootTarget = normalizeNumberValue(entry.digitalRoot); if (detailNameEl) { detailNameEl.textContent = `Number ${normalized} · ${entry.label}`; } if (detailTypeEl) { detailTypeEl.textContent = `Opposite: ${opposite}`; } if (detailSummaryEl) { detailSummaryEl.textContent = entry.summary || ""; } renderNumbersSpecialPanel(context, normalized, entry); if (!detailBodyEl) { return; } detailBodyEl.replaceChildren(); const pairCardEl = document.createElement("div"); pairCardEl.className = "numbers-detail-card"; const pairHeadingEl = document.createElement("strong"); pairHeadingEl.textContent = "Number Pair"; const pairTextEl = document.createElement("div"); pairTextEl.className = "numbers-detail-text"; pairTextEl.textContent = `Opposite: ${opposite}`; const keywordText = entry.keywords.length ? `Keywords: ${entry.keywords.join(", ")}` : "Keywords: --"; const pairKeywordsEl = document.createElement("div"); pairKeywordsEl.className = "numbers-detail-text numbers-detail-text--muted"; pairKeywordsEl.textContent = keywordText; const oppositeBtn = document.createElement("button"); oppositeBtn.type = "button"; oppositeBtn.className = "numbers-nav-btn"; oppositeBtn.textContent = `Open Opposite Number ${opposite}`; oppositeBtn.addEventListener("click", () => { selectNumberEntry(opposite); }); pairCardEl.append(pairHeadingEl, pairTextEl, pairKeywordsEl, oppositeBtn); const kabbalahCardEl = document.createElement("div"); kabbalahCardEl.className = "numbers-detail-card"; const kabbalahHeadingEl = document.createElement("strong"); kabbalahHeadingEl.textContent = "Kabbalah Link"; const kabbalahNode = Number(entry?.associations?.kabbalahNode); const kabbalahTextEl = document.createElement("div"); kabbalahTextEl.className = "numbers-detail-text"; kabbalahTextEl.textContent = `Tree node target: ${kabbalahNode}`; const kabbalahBtn = document.createElement("button"); kabbalahBtn.type = "button"; kabbalahBtn.className = "numbers-nav-btn"; kabbalahBtn.textContent = `Open Kabbalah Tree Node ${kabbalahNode}`; kabbalahBtn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:kabbalah-path", { detail: { pathNo: kabbalahNode } })); }); kabbalahCardEl.append(kabbalahHeadingEl, kabbalahTextEl, kabbalahBtn); const alphabetCardEl = document.createElement("div"); alphabetCardEl.className = "numbers-detail-card"; const alphabetHeadingEl = document.createElement("strong"); alphabetHeadingEl.textContent = "Alphabet Links"; const alphabetLinksWrapEl = document.createElement("div"); alphabetLinksWrapEl.className = "numbers-links-wrap"; const alphabetLinks = getAlphabetPositionLinksForDigitalRoot(context, rootTarget); if (!alphabetLinks.length) { const emptyAlphabetEl = document.createElement("div"); emptyAlphabetEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyAlphabetEl.textContent = "No alphabet position entries found for this digital root yet."; alphabetLinksWrapEl.appendChild(emptyAlphabetEl); } else { alphabetLinks.forEach((link) => { const button = document.createElement("button"); button.type = "button"; button.className = "numbers-nav-btn"; button.textContent = `${link.alphabet}: ${link.label}`; button.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:alphabet", { detail: link.detail })); }); alphabetLinksWrapEl.appendChild(button); }); } alphabetCardEl.append(alphabetHeadingEl, alphabetLinksWrapEl); const tarotCardEl = document.createElement("div"); tarotCardEl.className = "numbers-detail-card"; const tarotHeadingEl = document.createElement("strong"); tarotHeadingEl.textContent = "Tarot Links"; const tarotLinksWrapEl = document.createElement("div"); tarotLinksWrapEl.className = "numbers-links-wrap"; const tarotCards = getTarotCardsForDigitalRoot(context, rootTarget, entry); if (!tarotCards.length) { const emptyEl = document.createElement("div"); emptyEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyEl.textContent = "No tarot numeric entries found yet for this root. Add card numbers to map them."; tarotLinksWrapEl.appendChild(emptyEl); } else { tarotCards.forEach((card) => { const button = document.createElement("button"); button.type = "button"; button.className = "numbers-nav-btn"; button.textContent = `${card.name}`; button.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:tarot-trump", { detail: { cardName: card.name } })); }); tarotLinksWrapEl.appendChild(button); }); } tarotCardEl.append(tarotHeadingEl, tarotLinksWrapEl); const calendarCardEl = document.createElement("div"); calendarCardEl.className = "numbers-detail-card"; const calendarHeadingEl = document.createElement("strong"); calendarHeadingEl.textContent = "Calendar Links"; const calendarLinksWrapEl = document.createElement("div"); calendarLinksWrapEl.className = "numbers-links-wrap"; const calendarLinks = getCalendarMonthLinksForNumber(context, normalized); if (!calendarLinks.length) { const emptyCalendarEl = document.createElement("div"); emptyCalendarEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyCalendarEl.textContent = "No calendar months currently mapped to this number."; calendarLinksWrapEl.appendChild(emptyCalendarEl); } else { calendarLinks.forEach((link) => { const button = document.createElement("button"); button.type = "button"; button.className = "numbers-nav-btn"; button.textContent = `${link.calendarLabel}: ${link.monthName} (Month ${link.monthOrder})`; button.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:calendar-month", { detail: { calendarId: link.calendarId, monthId: link.monthId } })); }); calendarLinksWrapEl.appendChild(button); }); } calendarCardEl.append(calendarHeadingEl, calendarLinksWrapEl); detailBodyEl.append(pairCardEl, kabbalahCardEl, alphabetCardEl, tarotCardEl, calendarCardEl); } window.NumbersDetailUi = { renderNumberDetail }; })();